Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Presubmit Script Gate

Pattern

A named solution to a recurring problem.

Directory-scoped executable policy: PRESUBMIT.py files run before upload, before commit, and inside the commit queue so Chromium can reject local rule violations before a CL lands.

A Chromium CL can fail before a reviewer opens it. depot_tools follows the changed paths, loads each applicable PRESUBMIT.py, and runs the directory’s checks during upload and again before commit. The rule lives beside the code it protects: generated files must be refreshed, forbidden APIs stay out of a subtree, and security-sensitive code can require a stronger assertion form. PRESUBMIT.py turns those local rules into source-controlled executable policy instead of a checklist only experienced contributors remember.

Context

This pattern sits between OWNERS File Governance and the Commit Queue Gate. OWNERS decides which humans have authority over a changed path. The commit queue decides whether an approved patch set can land. PRESUBMIT.py gives each directory an executable veto before review or CQ treats the CL as landable.

The mechanism belongs to depot_tools as much as to the Chromium source tree. When a contributor runs git cl upload, depot_tools computes the affected files, walks from each file’s directory up through its ancestors, loads each applicable PRESUBMIT.py, and calls upload-time entry points such as CheckChangeOnUpload. Later, git cl commit and the commit queue run commit-time checks such as CheckChangeOnCommit. The gate is path-sensitive: a rule in content/PRESUBMIT.py applies under content/; a rule in a deeper directory can add narrower checks for that subtree.

Problem

Chromium has too many local invariants for a central checklist to carry. Generated bindings must match changed IDL files. APIs that are dangerous in one subsystem may be acceptable elsewhere. Metadata ownership rules differ by path. Security review doctrine sometimes belongs in the directory that sees the recurring mistake, not in a reviewer comment repeated across Gerrit threads.

The recurring problem is that local rules need to be mandatory without becoming tribal knowledge. A reviewer can miss a generated artifact. A new contributor can’t know every directory’s conventions. An AI coding agent can produce a patch that compiles while violating a subsystem rule no compiler sees. The project needs the rule to run where the changed files are, before the change consumes reviewer time or enters CQ.

Forces

  • Local policy changes faster than central infrastructure. A subsystem can add a presubmit check in the same tree it protects, without waiting for a global linter rollout.
  • The check must run before it burns reviewer attention. A generated-file mismatch or forbidden API use should fail at upload, not after a reviewer has spent thirty minutes reading the CL.
  • The rule must run where local state differs. A contributor’s checkout can be stale, modified, or incomplete. CQ has to rerun presubmit against the patch set it is about to land.
  • Escape hatches are sometimes necessary. Hooks can be bypassed for emergency or infrastructure work, but the bypass has to be explicit and visible.
  • Presubmit is not a substitute for tests. A Python check over changed files catches local policy violations; it doesn’t prove the product behavior is correct.

Solution

Encode directory-local policy in PRESUBMIT.py and let depot_tools run the applicable checks at upload and commit time. A presubmit file exports Python functions whose names tell the executor when to call them. CheckChangeOnUpload(input_api, output_api) runs during upload-oriented workflows such as git cl upload and git cl presubmit --upload. CheckChangeOnCommit(input_api, output_api) runs before commit-oriented workflows such as git cl presubmit, git cl commit, and CQ processing. Shared helper functions carry rules that apply in both phases.

The check returns result objects through output_api. PresubmitError blocks progress. PresubmitPromptWarning asks the contributor to acknowledge or fix the issue. PresubmitNotifyResult surfaces context without failing the operation. That distinction matters because presubmit is used for several kinds of policy: hard correctness gates, soft migration warnings, and contributor guidance that shouldn’t stop a CL.

Discovery is ancestor-scoped. For every changed file, depot_tools finds PRESUBMIT.py files in the file’s directory and its parents, then runs the relevant functions with an input_api view of the change. The script can inspect affected files, read file contents, call project tools, and return path-specific diagnostics. This gives Chromium a local rule system whose authority comes from source control: the rule lives next to the subsystem and is versioned with the code it governs.

The contributor-facing test path is explicit. git cl presubmit --upload exercises the upload-time checks. git cl presubmit exercises the commit-time checks. A contributor can run those commands before asking for review, and a reviewer can ask for the exact presubmit result rather than reconstructing a rule by hand.

The escape hatches are explicit too. --bypass-hooks skips local hook execution, and the CQ No-Presubmit: true footer suppresses presubmit in the commit queue. Both are exceptional signals, not ordinary workflow. A CL using them needs a reason visible to reviewers because it is asking Chromium to skip one of its executable policy layers.

The limit is as important as the mechanism. Local presubmit runs in the contributor’s checkout. If the checkout is stale, missing generated files, or carrying local modifications, the result can differ from CQ. That is why commit-time presubmit runs again inside CQ: upload-time success is evidence, not a landing guarantee.

How It Plays Out

A contributor changes a Mojo interface and forgets to refresh the generated bindings. The compiler would fail later, but the local directory’s presubmit sees the changed .mojom file, checks the generated outputs, and returns a fatal result before the CL is uploaded. The contributor runs the generator, uploads a new patch set, and the reviewer never spends attention on a mechanically incomplete change.

A team working under content/ adds a presubmit warning for navigation code that uses DCHECK where CHECK is preferred because a violated assumption could be security-sensitive. The rule doesn’t prove the code is safe. It names a local doctrine and makes the reviewer see the violation at upload or CQ time. That is the right scale for presubmit: it catches the shape of a known local mistake before the mistake becomes a tree-health or security-review problem.

A downstream vendor prepares a CL that passes local presubmit, then sees CQ reject the same patch for a presubmit failure. The two results can both be correct. CQ ran the checks against the patch set in a clean environment and a current tree. The vendor’s local checkout was stale and had not included a new ancestor PRESUBMIT.py added that morning. The right response is to sync, rerun git cl presubmit, fix the violation, and upload a new patch set. Local success doesn’t override CQ’s run.

Consequences

Benefits. Presubmit turns local project knowledge into executable policy. A directory owner can encode generated-file freshness, banned API use, metadata requirements, or security-sensitive conventions as code that travels with the subsystem. The check fires before review and before CQ, so many low-value review comments never need to be written. The mechanism is auditable: a contributor can read the PRESUBMIT.py file that rejected their CL and see the rule in source control.

The gate also gives AI coding agents a stronger target than “make the code compile.” An agent that knows a directory has presubmit policy can run git cl presubmit --upload before presenting a CL, read the specific diagnostics, and fix the changed files rather than asking a human to infer the missing local rule from a later CQ failure.

Liabilities. Presubmit can become a slow, noisy gate if teams put broad analysis into a path-scoped script. A check that shells out to expensive tools on every upload teaches contributors to bypass hooks; a warning that fires on harmless cases teaches reviewers to ignore it. Presubmit scripts are written in Python and run in contributor environments, so the rule itself has maintenance cost. It can rot when file layouts change, generated artifacts move, or a local convention stops being true.

The gate can produce false confidence. A CL that passes presubmit has cleared the directory’s executable policy, not the whole quality bar. It still needs the right OWNERS LGTM, trybot coverage, security review when applicable, and the commit queue’s current-tree evaluation. Presubmit is the first mechanical gate, not the last one.

Notes for Agent Context

Before presenting a Chromium CL that touches a directory with PRESUBMIT.py in its path ancestry, run git cl presubmit --upload for upload-time checks and git cl presubmit before claiming the change is ready for CQ. Treat fatal presubmit results as blockers and warning results as review-visible issues that need either a fix or a human-approved reason. Do not advise --bypass-hooks or No-Presubmit: true unless the human explicitly identifies an emergency or infrastructure case, and surface the bypass as a risk in the CL description. When CQ fails presubmit after a local pass, prefer “sync and rerun in a clean checkout” over retrying CQ; the CQ result is the gate that matters for landing.

Sources

Chromium’s Presubmit Scripts guide documents the PRESUBMIT.py discovery model, upload and commit entry points, result classes, local test commands, bypass behavior, and the limits of local execution. The depot_tools presubmit_support.py executor is the implementation source for finding relevant scripts and calling CheckChangeOnUpload or CheckChangeOnCommit. Chromium’s contributing guide and commit checklist place presubmit in the ordinary contributor workflow, before CQ Dry Run and Submit to CQ. The CQ documentation records the No-Presubmit: true footer and the fact that CQ runs presubmit as part of the landing gate. A 2026 Chromium commit adding content/PRESUBMIT.py for navigation DCHECK policy shows the mechanism’s current use for a security-sensitive local rule.

Technical Drill-Down