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?
What a gated deploy stage gives you
Section titled “What a gated deploy stage gives you”- An agent review on every PR, with findings ranked by the same
REVIEW.mdin 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.
Before you wire up agent review
Section titled “Before you wire up agent review”- 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.
Put an agent review on every pull request
Section titled “Put an agent review on every pull request”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:
name: Code Reviewon: 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).
Codex code review on GitHub is requested with @codex review (and @codex security review) on a PR, can run automatically once enabled, and takes custom review rules from AGENTS.md (verified on OpenAI’s GitHub integration page on 2026-08-28; the host was unreachable on 2026-09-26).
In your own CI, openai/codex-action@v1 runs codex exec on the runner. Give it a read-only permission profile and pass only the PR number and commit SHAs in the prompt:
name: Codex reviewon: pull_request: types: [opened, synchronize, ready_for_review, reopened]jobs: codex: runs-on: ubuntu-latest permissions: contents: read outputs: final_message: ${{ steps.run_codex.outputs.final-message }} steps: - uses: actions/checkout@v7 with: ref: refs/pull/${{ github.event.pull_request.number }}/merge persist-credentials: false - name: Fetch base and head env: PR_BASE_REF: ${{ github.event.pull_request.base.ref }} PR_NUMBER: ${{ github.event.pull_request.number }} run: git fetch --no-tags origin "$PR_BASE_REF" "+refs/pull/$PR_NUMBER/head" - id: run_codex uses: openai/codex-action@v1 with: openai-api-key: ${{ secrets.OPENAI_API_KEY }} permission-profile: ":read-only" prompt: | Review PR #${{ github.event.pull_request.number }} in ${{ github.repository }}. Only the changes in ${{ github.event.pull_request.base.sha }}...${{ github.event.pull_request.head.sha }}. Follow REVIEW.md at the repository root for passes, severity, and what to skip. post: needs: codex if: needs.codex.outputs.final_message != '' runs-on: ubuntu-latest permissions: pull-requests: write steps: - env: GH_TOKEN: ${{ github.token }} GH_REPO: ${{ github.repository }} PR: ${{ github.event.pull_request.number }} MSG: ${{ needs.codex.outputs.final_message }} run: gh pr comment "$PR" --body "$MSG"The post job is the only one with pull-requests: write, so the job that runs Codex never holds a token that can write to the PR; the review text reaches the shell through env, never inlined into the command. The action’s default safety-strategy is drop-sudo, which removes sudo from the runner user before Codex starts. Locally, codex review --base main reviews the branch against main, and /review in the TUI reviews your current changes.
Bugbot “reviews pull requests and identifies bugs, security issues, and code quality problems.” Enable it for the repository and let it comment on every PR. PR Routing & Approval “assigns reviewers based on code ownership and commit history, and can approve low-risk PRs when your criteria are met” (both verified on cursor.com on 2026-08-28).
If you turn on that approval, write the criteria so a PR in a high risk class is never approved by it, and confirm that branch protection still requires a human code owner. Bugbot pricing and its rule-file format could not be re-verified on 2026-09-26, so check them in Cursor’s Bugbot docs before rollout.
Cursor’s docs, as checked on 2026-08-28, describe no way to turn Bugbot’s findings into a severity count that a required check can gate on, the way the Claude Code Review gate under “Gate the merge on Important findings” does. Until you confirm one, make two things the gate: a required human code-owner review, and the IMPORTANT= line from the review prompt below, which you run in Agent before push.
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
## PassesRun 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 hereReserve Important for findings that would break behaviour, leak data,or breach a policy. Style, naming and refactoring suggestions are Nit at most.
## Cap the nitsReport at most five nits per review; mention the rest as a count in the summary.
## Re-reviewsAfter the first review of a PR, report Important findings only.
## Do not reportGenerated 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.
Gate the merge on Important findings
Section titled “Gate the merge on Important findings”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 pipefailSHA="$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 1fiThe 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:
name: Claude review gateon: 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.
Run the prompt below in the PR’s branch with the Codex CLI, or as a codex exec step in CI with a :workspace permission profile. Review threads are contributor-controlled text, so in CI split the work: the job that runs Codex checks out with persist-credentials: false, holds only contents: read, and uploads its changes as a patch artifact; a second job with contents: write and no agent applies the patch and pushes it to the PR branch. Add a ruleset or branch protection that blocks pushes to the default and release branches, so the only branch that job can change is the PR branch. GitHub tokens are scoped to repositories, not branches, so the ruleset is what does the narrowing.
Run the prompt below in Agent on the PR’s branch, or hand it to a Cloud Agent so the sweep runs on a remote machine while you keep working.
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:
claude mcp add -s project --transport http github https://api.githubcopilot.com/mcp/x/pull_requests \ -H 'Authorization: Bearer ${GITHUB_PAT}'codex mcp add github --url https://api.githubcopilot.com/mcp/x/pull_requests \ --bearer-token-env-var GITHUB_PAT{ "mcpServers": { "github": { "url": "https://api.githubcopilot.com/mcp/x/pull_requests", "headers": { "Authorization": "Bearer ${env: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.
Put the production gate outside the model
Section titled “Put the production gate outside the model”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:
| Layer | What it enforces | Why the agent cannot pass it |
|---|---|---|
Branch protection and CODEOWNERS | Every change arrives as a PR; a human code owner approves | The agent’s token has no approval power, and required checks run in CI |
| Deployment environment with required reviewers | A named release owner approves each production run | Approval happens in the CI platform, outside any agent session |
| Environment-scoped deploy credentials | Only the approved production job can deploy | The agent’s jobs and sessions never receive the credential |
| Agent hooks and managed settings | A deploy command typed in a session is refused with a reason | Early 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 secretThe 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." }}'fiexit 0deny 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.
Codex CLI 0.157.1 has hooks with 12 events, including PreToolUse and PermissionRequest; project hooks run only after you trust them. Put the same check in a PreToolUse hook. An administrator can set allow_managed_hooks_only = true in requirements.toml so only managed hooks run, and can constrain approval_policy and permission_profile there too. .rules files (execpolicy) control which commands may run outside the sandbox.
Cursor Hooks exchange JSON over stdio and run before or after stages of the agent loop, where they can block an action. Put the same pattern match in a hook that runs before shell commands, and share it with the team the way Cursor’s hooks docs describe. This maps to the same PreToolUse pattern as the other two tabs: a script reads the pending command as JSON on stdin and answers with an allow or deny decision on stdout. The exact event names and response fields could not be re-verified on 2026-09-26 (cursor.com is not reachable from our verification environment); take them from Cursor’s hooks docs.
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”-
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 rungh 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 jsongh 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 theclaudecall 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. -
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.
-
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 defaultsafety-strategy: drop-sudoand prefer a permission profile over the legacysandboxinput. -
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
actionstoolset (https://api.githubcopilot.com/mcp/x/actions/readonly) lets the agent read run status and logs without being able to start a run. -
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:
| Environment | The agent may | The gate |
|---|---|---|
| Local or per-task sandbox | Run the full deploy procedure with scoped, short-lived credentials | The sandbox boundary |
| Staging | Trigger the pipeline deploy and run smoke tests | Pipeline checks; a hook ask in interactive sessions |
| Production | Prepare the evidence, open the release PR, draft the changelog | Environment 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”| Symptom | Cause | Recovery |
|---|---|---|
| Reviews bury the team in nits | No severity policy, no cap | Add the Important definition, a nit cap, and the re-review rule to REVIEW.md; exclude generated paths |
| A PR merged with an open Important finding | The review check is neutral, and nobody gated on it | Make the severity gate a required check |
| Fork PRs get no review | Secrets are withheld from fork runs; managed Code Review reviews forks only on @claude review | Comment @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 PR | PR title, body, or code comments were pasted into the reviewer’s prompt | Pass only numbers and SHAs in the prompt, and keep the reviewer read-only |
| The authoring agent makes checks green by weakening tests | Tests live inside the diff the agent controls | Lock test paths with CODEOWNERS and CI-owned checks: see protecting the oracle |
| The review loop never converges | Each fix triggers a new round of findings | Re-review rule in REVIEW.md, a three-round cap in the sweep prompt, then escalate to a human |
| A deploy slipped past the hook | The command was spelled differently | Nothing to patch in the hook; confirm the credential was missing and the environment approval held, and fix whichever did not |
| Managed Code Review is unavailable | ZDR organization, or traffic must stay on Bedrock, Google Cloud, or Foundry | Run claude-code-action in your own CI with the matching use_* input |
Where to go next after the deploy stage
Section titled “Where to go next after the deploy stage”Tool-specific setup: Claude Code, Codex, and Cursor.