mirror of
https://github.com/anthropics/claude-code-action.git
synced 2026-08-22 03:18:54 +08:00
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>
This commit is contained in:
co-authored by
Claude
Octavian Guzu
parent
fad22eb3fa
commit
846d5d8993
@@ -0,0 +1,123 @@
|
||||
# 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
|
||||
(12–40 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.
|
||||
Reference in New Issue
Block a user