Skip to content

Deploy: review in both directions, gate production

The deploy stage runs an agent review on every pull request, lets the authoring agent fix its own review findings, and keeps merge and production behind controls that live outside the model: branch protection with a code owner, a deployment environment with required reviewers, and deploy credentials the agent never holds. Agent hooks only catch mistakes early.

Your agents opened more pull requests last week than the team could read. Two sat for three days waiting for a reviewer, and one merged after a skim and paged someone on Saturday. This page is for the developer who wires agent review into the repository and the tech lead who owns the merge and release gates.

“Review in both directions” means two loops. Inbound, a review agent checks every PR the same way and ranks what it finds. Outbound, the agent that wrote a PR works through the review threads on it until only a human decision is left. Human attention moves up a level: does the change do what spec.md intended, and is the risk acceptable?

  • An agent review on every PR, with findings ranked by the same REVIEW.md in every managed or CI run.
  • A CI step that fails the merge when the review reports an Important finding.
  • Two copy-paste prompts: one that reviews a branch against its spec before push, and one that sweeps unresolved review threads on the agent’s own PR.
  • A production gate built from branch protection, a deployment environment, and scoped credentials, with agent hooks as an early warning rather than the lock.
  • A per-environment table that says how far an agent may go in local, staging, and production.
  • Test evidence from the Test stage. Review ranks findings; it does not replace a failing-closed test loop.
  • An evidence bundle on every agent PR: spec link, test output, risk class, and sensitive paths touched. The reviewer and the human both read the bundle before the diff.
  • Branch protection on the default branch that requires a pull request, passing checks, and approval from a code owner listed in CODEOWNERS.
  • A named release owner for production, and the risk classes in the governance policy that decide which changes need a human beyond the code owner.

Start with one reviewer per repository: in our experience a second reviewer on the same PR adds threads faster than it adds distinct findings.

Managed Code Review is the fastest start: an Owner enables it for the organization, and each repository chooses a trigger (once after PR creation, after every push, or manual). It is a research preview for Team and Enterprise plans, it is not available to Zero Data Retention organizations, and Anthropic’s docs say each review “averages $15-25” in usage credits (checked 2026-09-26). Comment @claude review on a PR to request one review on demand, or @claude review always to subscribe the PR to reviews on later pushes.

Code Review posts inline comments tagged Important or Nit and fills a check run named Claude Code Review. That check always finishes with a neutral conclusion, so it never blocks a merge on its own. The next section turns it into a gate.

In your own CI (needed for Amazon Bedrock, Google Cloud’s Agent Platform (formerly Vertex AI), Microsoft Foundry, or a ZDR organization), run the code-review plugin through anthropics/claude-code-action@v1:

.github/workflows/claude-review.yml
name: Code Review
on:
pull_request:
types: [opened, synchronize, ready_for_review, reopened]
jobs:
review:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
issues: read
id-token: write
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
fetch-depth: 1
- uses: anthropics/claude-code-action@v1
with:
anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
plugin_marketplaces: "https://github.com/anthropics/claude-code.git"
plugins: "code-review@claude-code-plugins"
prompt: "/code-review:code-review --comment ${{ github.repository }}/pull/${{ github.event.pull_request.number }}"
claude_args: '--allowedTools "mcp__github_inline_comment__create_inline_comment"'

Keep the claude_args line: the action starts the inline-comment server only when --allowedTools names it. For a cloud provider, set use_bedrock, use_vertex, or use_foundry to "true" instead of the API key. The plugin skips a PR that already has a Claude comment, so later pushes are not re-reviewed; use managed Code Review with the every-push trigger when you need re-reviews. Before push, run the review prompt below, which reads REVIEW.md; local /code-review does not (checked on v2.1.283).

The per-tool setup pages go deeper: Claude Code review automation, Codex code review, and Bugbot.

Write REVIEW.md so every run ranks findings the same way

Section titled “Write REVIEW.md so every run ranks findings the same way”

A review agent with no written policy flags whatever its defaults favour, so two runs on similar PRs disagree about what matters. Put the policy in one file at the repository root. Claude Code’s managed Code Review reads REVIEW.md directly; the Codex workflow above points to it in its prompt; for @codex review, copy the rules into AGENTS.md.

# Review instructions
## Passes
Run three passes and tag each finding with its pass:
- Bugs: logic errors, broken edge cases, subtle regressions
- Security: injection, authentication and authorization gaps, secrets or PII in logs
- Compliance: the change matches spec.md and plan.md for this PR; flag any
behaviour the spec does not mention
## What Important means here
Reserve Important for findings that would break behaviour, leak data,
or breach a policy. Style, naming and refactoring suggestions are Nit at most.
## Cap the nits
Report at most five nits per review; mention the rest as a count in the summary.
## Re-reviews
After the first review of a PR, report Important findings only.
## Do not report
Generated files under src/gen/, lockfiles, and anything CI already enforces
(formatting, lint, type errors).

The re-review rule matters once the authoring agent starts pushing fixes: without it, every fix commit invites a fresh round of nits, and the PR never converges.

Review findings do not approve or block anything by themselves. Keep the human code owner as the approval. Add a required check that fails when the review reports an Important finding, so the code owner never sees a PR with an open bug the reviewer already found.

For Claude Code’s managed review, the last line of the check run’s output carries machine-readable severity counts. This script reads them for one commit and fails when any Important finding is open:

#!/bin/bash
# .github/scripts/claude-review-gate.sh HEAD_SHA (needs gh and jq; GH_TOKEN set)
set -euo pipefail
SHA="$1"
ID=$(gh api "repos/$GITHUB_REPOSITORY/commits/$SHA/check-runs" \
--jq '.check_runs[] | select(.name == "Claude Code Review") | .id' | head -n1)
[ -n "$ID" ] || { echo "No Claude Code Review check run for $SHA" >&2; exit 1; }
COUNTS=$(gh api "repos/$GITHUB_REPOSITORY/check-runs/$ID" \
--jq '.output.text | split("bughunter-severity: ")[1] | split(" -->")[0] | fromjson') \
|| { echo "No severity counts: the review failed or timed out. Comment '@claude review' to re-run it." >&2; exit 1; }
IMPORTANT=$(echo "$COUNTS" | jq '.normal')
echo "Review severity counts: $COUNTS"
if [ "$IMPORTANT" -gt 0 ]; then
echo "Blocked: $IMPORTANT Important finding(s). Fix them or record why each one is wrong." >&2
exit 1
fi

The normal key is the count of Important findings; nit and pre_existing never fail the gate.

Run it from a workflow that fires when the Claude Code Review check run completes. A check_run workflow runs on the default branch, so its own job never attaches to the PR’s head commit. Instead, the job posts a commit status to the reviewed SHA:

.github/workflows/claude-review-gate.yml
name: Claude review gate
on:
check_run:
types: [completed]
jobs:
gate:
if: github.event.check_run.name == 'Claude Code Review'
runs-on: ubuntu-latest
permissions:
contents: read
checks: read
statuses: write
steps:
- uses: actions/checkout@v7 # default branch: the gate script never comes from the PR
with:
persist-credentials: false
- env:
GH_TOKEN: ${{ github.token }}
SHA: ${{ github.event.check_run.head_sha }}
run: |
if bash .github/scripts/claude-review-gate.sh "$SHA"; then STATE=success; else STATE=failure; fi
gh api "repos/$GITHUB_REPOSITORY/statuses/$SHA" \
-f state="$STATE" -f context=claude-review-gate \
-f description="Important findings from Claude Code Review"

In branch protection, make claude-review-gate a required status check. The gate needs a review on every head commit, so set the repository’s trigger to After every push, or comment @claude review always on the PR; under Once after PR creation or Manual, the authoring agent’s fix commits get no review, and the required status never arrives. Fork PRs are never re-reviewed on push, so each new head commit on a fork PR needs a fresh @claude review comment before the gate can pass. A failed or timed-out review has no severity line, so the gate fails closed; comment @claude review to re-run the review (Anthropic’s Code Review docs, checked 2026-09-26).

For Codex, have the review emit a machine-readable verdict in the same spirit (for example, codex exec --output-schema with a JSON schema that includes an important count) and gate on that. For Bugbot, use the code-owner gate described in the Cursor tab above.

Let the authoring agent close its own review threads

Section titled “Let the authoring agent close its own review threads”

The outbound loop costs human time on every PR: a reviewer leaves six comments, and the author spends an afternoon on them. Hand that to the agent that wrote the PR, with a hard rule that it can push commits but never approve, merge, or dismiss a review.

With the anthropics/claude-code-action@v1 mention workflow installed (/install-github-app sets it up), comment @claude on the PR with the request; Claude replies in the thread and can push a fix commit. Replying to a managed Code Review comment does not trigger anything: push the fix, and a push-triggered review resolves the thread. Locally, run the prompt below in the PR’s worktree.

If the agent should read threads through an API rather than gh, the GitHub MCP Server exposes pull-request review threads, replies and thread resolution as tools. Load only its pull_requests toolset through the URL path:

Terminal window
claude mcp add -s project --transport http github https://api.githubcopilot.com/mcp/x/pull_requests \
-H 'Authorization: Bearer ${GITHUB_PAT}'

Each config reads the token from the GITHUB_PAT environment variable rather than holding it in the file; Cursor’s MCP docs describe the ${env:NAME} form, so confirm it expands on your version. Context cost: every tool in a loaded toolset adds its definition to the session, so one toolset keeps it small, and gh from the shell costs fewer tokens still. The token decides what the server can do, so scope it to this one repository and grant pull-request write only to a session that must reply to threads.

Two settings keep that loop honest. On GitHub, turn off Allow GitHub Actions to create and approve pull requests so a workflow token cannot approve anything. And give the agent its own identity, so the PR history shows which commits the agent pushed and which a person did: see agent identity and secrets.

When a review flags the same mistake a second time, the fix includes a line in CLAUDE.md, AGENTS.md, or Cursor Rules, so the next build session does not repeat it. That is the feedback edge back to Build.

The agent may act up to the production gate and cannot pass it. The gate that holds is the one the agent cannot edit, reword, or talk its way around. Build it in layers, strongest first:

LayerWhat it enforcesWhy the agent cannot pass it
Branch protection and CODEOWNERSEvery change arrives as a PR; a human code owner approvesThe agent’s token has no approval power, and required checks run in CI
Deployment environment with required reviewersA named release owner approves each production runApproval happens in the CI platform, outside any agent session
Environment-scoped deploy credentialsOnly the approved production job can deployThe agent’s jobs and sessions never receive the credential
Agent hooks and managed settingsA deploy command typed in a session is refused with a reasonEarly warning only: it matches command text, not intent

On GitHub Actions, the environment carries the gate. Configure required reviewers and Prevent self-review under Settings › Environments › production, and store the deploy token as an environment secret, which the job receives only after approval:

# .github/workflows/deploy.yml (excerpt)
jobs:
deploy-production:
needs: [build, test]
runs-on: ubuntu-latest
environment: production # required reviewers approve here
permissions:
contents: read
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- run: ./scripts/deploy.sh production
env:
DEPLOY_TOKEN: ${{ secrets.PROD_DEPLOY_TOKEN }} # environment secret, not a repository secret

The hook sits on top as the early warning. It refuses a production deploy typed into a session and tells the agent the route that does work:

Register the script as a PreToolUse hook with matcher Bash in .claude/settings.json, as on the Build page, or in managed settings so engineers cannot switch it off (allowManagedHooksOnly restricts a machine to managed hooks).

#!/bin/bash
# .claude/hooks/production-gate.sh (chmod +x; needs jq)
CMD=$(jq -r '.tool_input.command // empty')
if echo "$CMD" | grep -Eqi '(deploy|release|promote).*(prod|production)'; then
jq -n '{hookSpecificOutput: {
hookEventName: "PreToolUse",
permissionDecision: "deny",
permissionDecisionReason: "Production deploys run only through the release pipeline. Update the PR and its evidence bundle; the release owner promotes it."
}}'
fi
exit 0

deny shows the reason to Claude, so it changes course instead of retrying. For staging, return "ask" instead: that forces a confirmation prompt even in auto mode. Remember who answers it, though: the developer at the keyboard, not the release owner. In claude -p runs nobody is there to answer, so an ask becomes a refusal in practice.

Production approval gate walks through the environment setup in detail, and Permissions and sandboxes covers the session-side controls in all three tools.

Run agents in CI/CD without handing them production

Section titled “Run agents in CI/CD without handing them production”
  1. Start with read-only judgment steps. Triage a failed build, summarize a flaky test, or draft release notes. Pipe the evidence in; the agent needs no tools to read it:

    Terminal window
    # CI, after a failed run
    gh run view "$RUN_ID" --log-failed | claude -p --bare --setting-sources "" --strict-mcp-config "Triage this failed build: root cause, the file to change, and whether it is flaky. Do not propose a retry unless the log proves a transient failure." --output-format json
    gh run view "$RUN_ID" --log-failed | codex exec -c default_permissions=":read-only" "Triage this failed build: root cause, the file to change, and whether it is flaky."

    Codex appends piped input to the prompt as a <stdin> block. The failed run may have built a contributor’s branch, so the claude call loads no project settings, hooks, or MCP servers from the checkout (--bare --setting-sources "" --strict-mcp-config), and neither call is given a write-capable token.

  2. Add write steps behind the existing gates. Fixing lint, regenerating docs, and addressing review comments all end as commits on a PR branch. The agent has no route to push to the default branch.

  3. Sandbox every agent job. Use containers, a network policy, short-lived tokens scoped to one repository, and no standing production credentials. For openai/codex-action, keep the default safety-strategy: drop-sudo and prefer a permission profile over the legacy sandbox input.

  4. Expose deploy, status, and rollback as scoped tools. When an agent needs to act on an environment, give it an MCP server or CLI wrapper per environment, so deployment power is an allowlist rather than a shell. For deploys that run as GitHub Actions workflows, the GitHub MCP server’s read-only actions toolset (https://api.githubcopilot.com/mcp/x/actions/readonly) lets the agent read run status and logs without being able to start a run.

  5. Rehearse rollback in staging until it is one command. Maintain calls that path when a control band is breached, and the rollback pipeline shows how to build it. Roll out in stages with progressive delivery so a bad change reaches a slice of traffic first.

How far an agent may go depends on the environment:

EnvironmentThe agent mayThe gate
Local or per-task sandboxRun the full deploy procedure with scoped, short-lived credentialsThe sandbox boundary
StagingTrigger the pipeline deploy and run smoke testsPipeline checks; a hook ask in interactive sessions
ProductionPrepare the evidence, open the release PR, draft the changelogEnvironment approval by the release owner; credentials only in the approved job

How do you know the deploy stage works without reading every diff?

Section titled “How do you know the deploy stage works without reading every diff?”

Test the gates the way an attacker would test a lock, once, on purpose, and again after every change to the pipeline:

  • Open a PR from an agent branch. A review check run appears, and findings arrive as inline comments.
  • Seed a known bug (an unguarded null, a missing authorization check). The review reports it as Important, and the merge gate fails.
  • Try to merge without a code-owner approval. Branch protection refuses.
  • Ask the agent to deploy to production. The hook refuses with its reason, and the agent’s jobs have no production credential to try anyway.
  • Trigger the production job yourself. It waits for the release owner, and the person who triggered it cannot approve it.

Then watch the loop over time. Leading indicators: time to first review, and the share of review threads the authoring agent closed without a human commit. Lagging indicators: defects and vulnerabilities caught before merge against those that escaped to production, plus change failure rate and time to restore, which your CI and incident tooling already record. Metrics for an AI-native SDLC defines them per stage.

What breaks in agent review and release gates, and how to recover

Section titled “What breaks in agent review and release gates, and how to recover”
SymptomCauseRecovery
Reviews bury the team in nitsNo severity policy, no capAdd the Important definition, a nit cap, and the re-review rule to REVIEW.md; exclude generated paths
A PR merged with an open Important findingThe review check is neutral, and nobody gated on itMake the severity gate a required check
Fork PRs get no reviewSecrets are withheld from fork runs; managed Code Review reviews forks only on @claude reviewComment @claude review on fork PRs, or review them in a trusted workflow that never runs the fork’s code with secrets
The review follows instructions hidden in the PRPR title, body, or code comments were pasted into the reviewer’s promptPass only numbers and SHAs in the prompt, and keep the reviewer read-only
The authoring agent makes checks green by weakening testsTests live inside the diff the agent controlsLock test paths with CODEOWNERS and CI-owned checks: see protecting the oracle
The review loop never convergesEach fix triggers a new round of findingsRe-review rule in REVIEW.md, a three-round cap in the sweep prompt, then escalate to a human
A deploy slipped past the hookThe command was spelled differentlyNothing to patch in the hook; confirm the credential was missing and the environment approval held, and fix whichever did not
Managed Code Review is unavailableZDR organization, or traffic must stay on Bedrock, Google Cloud, or FoundryRun claude-code-action in your own CI with the matching use_* input

Tool-specific setup: Claude Code, Codex, and Cursor.