The gate

One check, five places

The gate decides if a description follows the convention. It is plain code: no AI, no network. The same code runs as a Claude Code hook, as a GitHub Action, in an Azure pipeline, in a rewrite job and from the command line. A failure says what is wrong and how to fix it.

What the gate is

pr-brief has two halves. The writer is an AI: the /pr-brief skill in Claude Code reads the change and writes the Brief and the review guide. The gate is the other half. It is deterministic code in the pr-brief binary. Given a description, it answers pass or fail, and it lists what to fix. Nothing in it calls a model, and it makes no network call.

That split is on purpose. A writer can make mistakes. The gate makes every description that reaches review follow the same shape, whoever or whatever wrote it: a person, Claude, or a bot.

  • Input: the text of the description, plus the settings (style, theme) and the host (GitHub or Azure DevOps).
  • Output: a list of findings, each with a rule name and a fix. An empty list is a pass.
  • Exit code: 0 for a pass, 1 for findings, 2 for a usage or config error.

The gate checks only the text between <!-- pr-brief:begin --> and <!-- pr-brief:end -->, plus the total length and the hidden previous-description block. Anything a person writes outside the markers is left alone.

Where it runs

WhereWhenWhat it does with a failure
Claude Code hookBefore gh pr create|edit, az repos pr create|update and the GitHub and Azure DevOps MCP create or update PR toolsDenies the call and lists the findings. Run /pr-brief to fix them.
GitHub Action (gagoar/pr-brief)When a PR is opened, edited, pushed to, reopened or marked readyFails the check with one annotation for each finding, and a job summary. Reads the description from the event payload.
Azure pipeline (.azuredevops/pr-brief.yml)As a Build Validation policy on the target branchFails the build with one ##vso[task.logissue] error for each finding. Reads the description over the Azure DevOps API.
The rewrite jobAfter the agent writes a description (see Rewriting in CI)The gate job re-reads the live description. The hook also gates the agent’s own gh pr edit.
Command lineWhenever you run itpr-brief gate --file body.md (or --stdin). Add --json for machine output and --host to pick the length limit.

The rules

Each finding starts with the name of the rule that raised it. The gate checks the parts in this order and reports every finding it can in one run. Three cases stop it early, because the later checks depend on them: a skip line with no reason, markers that are missing or broken, and sections that are wrong or out of order.

RuleWhat it checksThe usual fix
lengthThe whole description is at most 65,536 characters on GitHub and 4,000 on Azure DevOps. It counts characters, not bytes. It runs first, so it applies to a skipped PR too.Cut the earlier description, fold “Other changed files”, or split the PR.
skipA > pr-brief skipped: line has a reason.Write the reason after the colon.
markersThe begin and end markers are present, in order, version v1, and name a style and a theme. A theme that is not built in must be named by the repo’s config.Run /pr-brief. Do not write markers by hand.
previousThe hidden block that keeps the earlier description is well formed and comes after the end marker.Use pr-brief body improve, which encodes it.
sectionsThe sections are ## Brief, ## Change map, ## Review guide, in that order, each once.Restore the three headings.
briefThe Brief is not empty, is one paragraph, and has at most 5 sentences.Shorten it to one paragraph.
diagramOne graph LR diagram per flow, at most 3, each under a ### Flow N heading. Boxes Input, Functions and Output. At most 9 nodes and 14 edges. Labels at most 28 characters, with no parentheses. Input and Output boxes read Input 1, Output 1. At most 2 context nodes. A small or config-only PR may use <!-- pr-brief:no-diagram: reason -->.Run pr-brief diagram and paste its output.
styleThe diagram’s init line, classDef lines, linkStyle lines, and layout links equal what the theme requires, to the character. The Legend line directly under the chart names the colours in use, and must equal what pr-brief diagram prints.Run pr-brief diagram and paste its output.
referencesA References table sits directly under each diagram. Every Input n and Output n in the diagram has a row, no row is unused, and an id means one thing in the whole description. A later flow writes see Flow 1.Add the missing row, or remove the unused one.
reviewThe Review guide has What changed, Read these first (a table of 1 to 7 rows) and Review order. Each file in the table is a link that points at that file, in the PR or on the branch. A link pinned to a commit, a range of commits or an older iteration fails, because it would show a state that newer commits have replaced.Add the part, or fix the row count. For a link, run pr-brief links --body FILE --pr N.
steWith style ste or ste+iceberg, the prose has no hard ASD-STE100 violations. Style iceberg skips this rule.Rewrite the sentence the finding quotes.

The numbers come from one place in the code, internal/convention, and the convention page shows the same ones. A test fails if they differ.

Reading a finding

A finding is one line: FAIL [rule] message. The message names the diagram or the id, says what it found, and says what it expected. A real one:

FAIL [style] diagram 1: the style does not match theme github-dark at style line 3.
      expected: classDef added fill:#12221d,stroke:#3fb950,stroke-width:1px,color:#e6edf3,font-weight:500
      found:    classDef added fill:#12221d,stroke:#3fb951,stroke-width:1px,color:#e6edf3,font-weight:500
      Run `pr-brief diagram` and paste its output.

Each place shows the same findings in its own form:

  • Command line: FAIL [rule] message lines, or JSON with --json. A warn: line is not a failure. For example, it says the STE lint is not available.
  • GitHub Action: one ::error annotation for each finding, and a job summary.
  • Azure pipeline: one ##vso[task.logissue type=error] line for each finding.
  • Hook: a denial that lists up to 12 findings. Claude Code shows it, and the skill fixes them and tries again.

What the gate reads

The rules above are fixed. Three inputs vary, and each has one source:

InputWhere it comes from
Style and themeThe repo’s .pr-brief.json, then your user file, then the default. In CI only the repo file counts, and the checkout is the base commit, so a PR cannot loosen its own rules. When the base branch has no .pr-brief.json, a workflow can pass style and theme. The file wins, and the check prints a notice when it overrides an input. When a config names a style or theme, the gate enforces it and ignores the choice in the marker. Without one, the marker’s built-in theme is accepted.
HostThe hook takes it from the command or the MCP tool (gh or GitHub means GitHub; az repos or an Azure DevOps tool means Azure DevOps). The Action is GitHub. A pipeline run (TF_BUILD) is Azure DevOps. On the command line, --host wins, then the CI variables, then git remote get-url origin. An unknown host gets the GitHub limit.
The descriptionThe tool call, the event payload, the Azure DevOps API, or a file. After a rewrite, the Action’s refresh input reads the live text.

The gate has no other switch. A bot-opened PR is skipped in CI, and a draft PR is skipped by the workflow.

The hook

The hook receives the tool call as JSON before Claude Code runs it. For a Bash call it looks for gh pr create|edit and az repos pr create|update, then reads the description from --body, --body-file, --description, a $(cat <<&#39;EOF&#39; ... EOF) heredoc, or $(cat file). For the MCP tools it reads the body or description field.

  • It never runs the text it reads. It only matches patterns, and it reads only files the command itself names.
  • It cannot read a shell variable or standard input. It denies the call and says to write the description to a file and use --body-file.
  • It makes no network calls, keeps no log and sends nothing.
  • A broken payload never blocks a call. The hook allows it and says why on standard error.
  • It adds about 16 ms to a Bash call.

In CI

Run /pr-brief ci in Claude Code. It asks which host you use and where the settings live, then writes the file. Or run pr-brief init --workflow yourself. Three details matter:

  • Check out the base commit. On a pull request event the default checkout is the merge commit, which contains the PR’s own .pr-brief.json. A PR could then loosen its own check. The base commit is safe because the gate reads only the config and the event payload, and never runs code from the PR.
  • Drafts and bots are skipped. Mark a PR ready, and the check runs.
  • No secret and no personal access token. The check needs only contents: read.

For Azure DevOps, run pr-brief init --workflow --host azure-devops. It writes .azuredevops/pr-brief.yml. Add it as a Build Validation policy on the target branch. It needs no secret: the job reads the rules from the target branch and the PR with its own System.AccessToken, and never checks out the PR’s code. It is tested against the Azure DevOps API, but not yet in a live pipeline.

Settings live in .pr-brief.json (the default, read from the base branch) or in the workflow: the Action inputs style and theme. Anyone who can edit the workflow in a PR can change workflow values, so use the file when that matters.

Annotations escape %, carriage returns and line feeds, so text in a description cannot end a workflow command or start another. The failing job also writes a summary that points at /pr-brief improve.

Rewriting the description in CI

The gate only checks. The writer is an AI, and by default it runs on a developer’s machine. A team can also let the Claude Code GitHub Action write the description when a PR opens. This is optional. It costs tokens on every PR, and it is GitHub only.

pr-brief init --rewrite --auth federation   # writes .github/workflows/pr-brief.yml

The file holds two jobs:

  1. rewrite runs when a PR is opened, marked ready, or given the label pr-brief. The agent runs the pr-brief skill in CI mode: it never asks a question, it takes its settings from the config, and it writes only the description with gh pr edit. It never changes the title or the code. The hook checks that write, so the agent cannot save a description that fails the gate.
  2. description is the gate. It runs on every event, after the rewrite job whatever that job did. It reads the live description with refresh, because an edit made with the job’s own token does not start another workflow run. Make this job the required check.

Each team chooses how the job signs in to Claude. The command needs --auth and has no default:

--authWhat it usesWhat you set up
federationAnthropic workload identity federation over GitHub OIDC. No stored secret.A federation rule and a service account, then the repository variables ANTHROPIC_FEDERATION_RULE_ID, ANTHROPIC_ORG_ID, ANTHROPIC_SERVICE_ACCOUNT_ID and ANTHROPIC_WORKSPACE_ID.
bedrockAn AWS role over GitHub OIDC. No stored secret.A role with Bedrock access. Variables AWS_ROLE_TO_ASSUME and AWS_REGION.
vertexA Google Cloud workload identity provider over GitHub OIDC. No stored secret.A provider and a service account with Vertex AI access. Variables GCP_WORKLOAD_IDENTITY_PROVIDER and GCP_SERVICE_ACCOUNT.
api-keyThe secret ANTHROPIC_API_KEY. This is a long-lived secret, not a federated identity.The repository secret.

The job is off until the credentials exist. A first step checks for them. If they are missing, it prints a notice and skips the rest of the job. The gate still runs. So the file can merge first, and the admin request for the identity can follow.

What keeps the job narrow:

  • It runs only for PRs from the same repository. A fork PR gets no secrets, and its code never reaches the agent.
  • The agent may run only git reads, gh pr view|diff|edit and pr-brief, and may read and write files. Text in a diff or a commit message is data for the agent to describe, never an instruction.
  • The asd-ste100 skill the default style needs is cloned at a pinned commit.
  • The gate job is read-only: contents: read and pull-requests: read, and it still checks out the base commit.

The agent works from the PR’s head, so the gate job, which reads the base branch’s config, is the check that counts. This workflow is not tested against a live repository yet. The first run may need a change to the allowed commands. Azure DevOps has no rewrite job, because the Claude Code GitHub Action runs on GitHub only.

When the gate fails

  1. Read the findings. The rule name tells you which part to look at, and the table above gives the usual fix.
  2. For a description that is wrong in many places, or that was never written by pr-brief, run /pr-brief improve <PR>. It saves the old description, writes a new one, shows you the difference and asks before it writes.
  3. For a chart, never edit the style by hand. Run pr-brief diagram and paste its output.
  4. To check a draft on your machine before you push it: pr-brief gate --file body.md.
  5. For a PR that should not carry a brief, use the skip line.

Skipping one PR

Some PRs should not carry a brief: a release PR, an automated bump. Put one visible line in the description:

> pr-brief skipped: release PR, notes are in the changelog

The reason is required and stays readable to reviewers. The gate still checks the length.

What it does not protect against

The gate is a quality control, not a security boundary. Anyone can edit a description in the web UI, call the REST API with gh api, or use the skip line. The gate keeps honest contributors consistent; it does not stop a determined one.

The threat model, including how the hook handles hostile input, is in SECURITY.md.