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

124 lines
7.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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`](../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**.
```yaml
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](./agent-identities.example.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.