claude-code-action/agent-approval-check
kashyap murali 846d5d8993
Add agent-approval-check composite action (#1429)
* Add agent-approval-check composite action

Require N human approvals on PRs that contain agent-authored commits.
Posts an agent-approval-check commit status that repos mark as a
required check on protected branches.

This is a sanitized port of the check Anthropic runs internally on
every agent-authored PR — same detection rules, /approve <sha>
comment flow, sibling-PR-same-SHA guard, and fail-closed semantics,
with the Anthropic-specific path exemptions and kill-switch removed
and config moved to action inputs.

Co-Authored-By: Claude <noreply@anthropic.com>

* Drop stray internal acronym from comment

* agent-approval-check: require write-access approvers, pin deps, pagination + doc fixes

🏠 Remote-Dev: homespace

* agent-approval-check: prettier

🏠 Remote-Dev: homespace

* agent-approval-check: verify approver write permission via REST; commits(last:100); docstring

🏠 Remote-Dev: homespace

* agent-approval-check: use headRefOid; drop pull_request_review trigger and correct threat-model docs

🏠 Remote-Dev: homespace

* agent-approval-check: stale-notification wording, no-retry-on-4xx, docstring API-call count

🏠 Remote-Dev: homespace

* agent-approval-check: fail-closed sibling guard on commits-ordering edge; drop stale 'reviewed' from README

🏠 Remote-Dev: homespace

* agent-approval-check: drop hardcoded API-call counts from logs; clarify author write-access requirement in README

🏠 Remote-Dev: homespace

* agent-approval-check: count all agent-email commits (close-reopen bypass); validate REQUIRED_APPROVALS>=1; exempt_head_branches warning

🏠 Remote-Dev: homespace

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Octavian Guzu <oct@anthropic.com>
2026-06-30 14:09:57 -07:00
..

Agent Approval Check

Require N human approvals on any pull request that contains commits authored by an AI agent (Claude, Claude Code, or any bot identity you configure). PRs without agent activity are unaffected.

This is the same gate Anthropic runs internally on every agent-authored PR.

What it does

When a PR is opened, pushed to, or commented on, this action:

  1. Scans the PR's commits, author, and reviews for the configured agent identities (committer email, bot login, or an APPROVED review from a bot). If none are found it posts success: No agent activity and stops.
  2. Counts distinct human approvals: the latest APPROVED review per login, plus any /approve <head-sha> comment whose SHA matches the current head. Only users with write access to the repo count (verified per-user via the collaborators permission API); agent and excluded-bot logins never count.
  3. Posts an agent-approval-check commit status (success once the count reaches required_approvals, otherwise pending) and a sticky PR comment explaining what's still needed.
  4. Re-evaluates on every new push or comment. A push moves the head SHA, so earlier /approve <old-sha> comments are flagged stale. Approving reviews still count toward the threshold — they're picked up the next time the workflow runs (on push or /approve); they just don't trigger a run on their own.

Mark agent-approval-check as a required status check on your protected branches and GitHub will refuse to merge until it's green.

Setup

Copy examples/agent-approval-check.yml into .github/workflows/ in your repo, then add agent-approval-check to the required status checks on your protected branch.

This action is designed to run alongside GitHub's native branch protection, not replace it. On the same protected branch you should also:

  1. Require at least 1 approving review from someone with write access.
  2. Enable Dismiss stale pull request approvals when new commits are pushed.
name: agent-approval-check
on:
  pull_request_target:
    types: [opened, synchronize, reopened, ready_for_review]
  issue_comment:
    types: [created]
permissions:
  contents: read
  pull-requests: write
  statuses: write
jobs:
  check:
    if: github.event_name != 'issue_comment' || github.event.issue.pull_request
    runs-on: ubuntu-latest
    steps:
      - uses: anthropics/claude-code-action/agent-approval-check@main
        with:
          required_approvals: 2
          agent_emails: noreply@anthropic.com
          agent_logins: claude[bot],claude-code[bot]

Inputs

Input Default Meaning
required_approvals 2 Distinct human approvals needed.
agent_emails noreply@anthropic.com Committer emails that mark a commit agent-authored.
agent_logins claude[bot],claude-code[bot] Logins treated as agents (PR author or approving reviewer).
excluded_approvers (empty) Logins whose approvals never count.
exempt_head_branches (empty) Head-branch globs that auto-pass. ⚠️ Leave empty — branch names are attacker-controlled, so this is not a safe place to encode trust.
exempt_path_prefixes (empty) PRs touching only these prefixes auto-pass.
protected_bases (default branch) Base branches this check gates (see threat model).
config_file (empty) Path to an agent-identities YAML replacing the inline inputs. See the warning below.
docs_url this README Link in the PR comment footer.
github_token ${{ github.token }} Needs statuses:write + pull-requests:write.

⚠️ config_file and checkout: if you set config_file, your workflow must check out the base branch to read it (the default behaviour of actions/checkout under pull_request_target). Never check out the PR head ref — doing so would let the PR author control the config and bypass this check.

Approving

A human counts as an approver by either:

  • submitting a normal GitHub Approve review, or
  • commenting /approve <sha> where <sha> is the current head commit (1240 hex chars). This path lets the PR author — who can't approve their own PR in GitHub's UI — vouch for commits an agent pushed on their behalf. The author's /approve is subject to the same write-access verification as any other approver, so a fork-PR author without write access on the base repository cannot self-count. The author counts as one approval; the remaining approvals must come from other reviewers with write access.

Threat model

  • Tamper-proof triggers. pull_request_target and issue_comment run the workflow file from the base/default branch, so the PR under review cannot edit this check. pull_request_review does not share this property — it runs from the merge ref — so the example workflow omits it; native Approve reviews are picked up on the next synchronize or /approve comment. This tamper-resistance assumes the workflow file itself is protected: an actor who can push workflow changes to the default branch can spoof any required status check, including this one, so protect .github/workflows/ via branch protection or CODEOWNERS.
  • Fail-closed. Any unhandled error exits non-zero; the required status stays non-success and the PR stays blocked. PRs with >100 commits are treated as agent-authored because the full commit list can't be verified.
  • Sibling-PR guard. Commit statuses attach to a SHA, not a PR. The action refuses to post a status on a PR whose base isn't in protected_bases, and withholds success while another open PR to a protected base shares the same head commit — otherwise a green status on one PR would also unblock the other.
  • No checkout of PR code. The action never checks out the PR's branch; it reads PR metadata via the GitHub API, so the usual pull_request_target code-execution risk does not apply.