mirror of
https://github.com/anthropics/claude-code-action.git
synced 2026-08-03 01:38:30 +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:
parent
fad22eb3fa
commit
846d5d8993
123
agent-approval-check/README.md
Normal file
123
agent-approval-check/README.md
Normal file
@ -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.
|
||||
72
agent-approval-check/action.yml
Normal file
72
agent-approval-check/action.yml
Normal file
@ -0,0 +1,72 @@
|
||||
name: Agent Approval Check
|
||||
description: |
|
||||
Require N human approvals on PRs that contain agent-authored commits
|
||||
(Claude, Claude Code, or any configured bot identity). Posts an
|
||||
`agent-approval-check` commit status — mark it as a required check on
|
||||
protected branches to gate merges.
|
||||
|
||||
inputs:
|
||||
github_token:
|
||||
description: Token with statuses:write and pull-requests:write on this repo.
|
||||
default: ${{ github.token }}
|
||||
required_approvals:
|
||||
description: Number of distinct human approvals required. Must be >= 1.
|
||||
default: "2"
|
||||
agent_emails:
|
||||
description: Comma-separated committer emails treated as agent-authored.
|
||||
default: noreply@anthropic.com
|
||||
agent_logins:
|
||||
description: |
|
||||
Comma-separated GitHub logins treated as agents — a PR opened by, or an
|
||||
APPROVED review from, one of these triggers the check.
|
||||
default: claude[bot],claude-code[bot]
|
||||
excluded_approvers:
|
||||
description: Comma-separated logins whose approvals never count (e.g. rubber-stamp bots).
|
||||
default: ""
|
||||
exempt_head_branches:
|
||||
description: |
|
||||
Comma-separated glob patterns; PRs from matching head branches auto-pass.
|
||||
WARNING: leave empty — branch names are attacker-controlled, so this is
|
||||
not a safe place to encode trust.
|
||||
default: ""
|
||||
exempt_path_prefixes:
|
||||
description: Comma-separated path prefixes; PRs touching only these auto-pass.
|
||||
default: ""
|
||||
protected_bases:
|
||||
description: |
|
||||
Comma-separated base branches this check gates. Empty = the repo's
|
||||
default branch only. PRs targeting any other base are refused (no
|
||||
status posted) so a sibling PR sharing the head SHA can't get the
|
||||
shared commit stamped green.
|
||||
default: ""
|
||||
config_file:
|
||||
description: Optional path to an agent-identities YAML file (overrides the inline inputs).
|
||||
default: ""
|
||||
docs_url:
|
||||
description: Link shown in the PR comment footer.
|
||||
default: "https://github.com/anthropics/claude-code-action/tree/main/agent-approval-check"
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5
|
||||
with:
|
||||
python-version: "3.12"
|
||||
- run: pip install 'httpx==0.28.1' 'pyyaml==6.0.3' 'tenacity==9.1.4'
|
||||
shell: bash
|
||||
- run: python "${{ github.action_path }}/agent_approval_check.py"
|
||||
shell: bash
|
||||
env:
|
||||
GH_TOKEN: ${{ inputs.github_token }}
|
||||
GH_REPOSITORY: ${{ github.repository }}
|
||||
GH_EVENT_NAME: ${{ github.event_name }}
|
||||
GH_EVENT_PATH: ${{ github.event_path }}
|
||||
REQUIRED_APPROVALS: ${{ inputs.required_approvals }}
|
||||
AGENT_EMAILS: ${{ inputs.agent_emails }}
|
||||
AGENT_LOGINS: ${{ inputs.agent_logins }}
|
||||
EXCLUDED_APPROVERS: ${{ inputs.excluded_approvers }}
|
||||
EXEMPT_HEAD_BRANCHES: ${{ inputs.exempt_head_branches }}
|
||||
EXEMPT_PATH_PREFIXES: ${{ inputs.exempt_path_prefixes }}
|
||||
PROTECTED_BASES: ${{ inputs.protected_bases }}
|
||||
CONFIG_FILE: ${{ inputs.config_file }}
|
||||
DOCS_URL: ${{ inputs.docs_url }}
|
||||
33
agent-approval-check/agent-identities.example.yaml
Normal file
33
agent-approval-check/agent-identities.example.yaml
Normal file
@ -0,0 +1,33 @@
|
||||
---
|
||||
# Optional config-file form of the agent-approval-check inputs.
|
||||
# Pass via `with: { config_file: .github/agent-identities.yaml }` instead of
|
||||
# the inline `agent_emails` / `agent_logins` / … inputs.
|
||||
|
||||
# Committer emails that mark a commit as agent-authored.
|
||||
agent_emails:
|
||||
- noreply@anthropic.com
|
||||
|
||||
# GitHub logins treated as agents — a PR opened by, or an APPROVED review
|
||||
# from, one of these triggers the check.
|
||||
agent_app_logins:
|
||||
- claude[bot]
|
||||
- claude-code[bot]
|
||||
|
||||
# Logins whose approvals never count toward the required total.
|
||||
excluded_approver_logins: []
|
||||
|
||||
# Head-branch glob patterns that auto-pass. Leave empty: branch names are
|
||||
# attacker-controlled, so this is not a safe place to encode trust.
|
||||
exempt_head_branches: []
|
||||
|
||||
# Per-repo path prefixes whose PRs auto-pass when ONLY those paths change.
|
||||
exempt_path_prefixes:
|
||||
owner/repo:
|
||||
- docs/
|
||||
|
||||
# Per-repo base branches this check gates. A repo with no entry defaults to
|
||||
# its default branch only. Listing a repo here REPLACES that default.
|
||||
protected_bases:
|
||||
owner/repo:
|
||||
exact: [main]
|
||||
prefixes: [release/]
|
||||
1828
agent-approval-check/agent_approval_check.py
Normal file
1828
agent-approval-check/agent_approval_check.py
Normal file
File diff suppressed because it is too large
Load Diff
39
examples/agent-approval-check.yml
Normal file
39
examples/agent-approval-check.yml
Normal file
@ -0,0 +1,39 @@
|
||||
# Require human approvals on PRs that contain agent-authored commits.
|
||||
#
|
||||
# Both triggers run the workflow file from the BASE/DEFAULT branch, so a PR
|
||||
# cannot edit this check to approve itself. (`pull_request_review` is not
|
||||
# used because it runs from the merge ref, not the default branch; native
|
||||
# Approve reviews are picked up on the next synchronize or `/approve`
|
||||
# comment.)
|
||||
#
|
||||
# After adding this workflow, mark `agent-approval-check` as a required
|
||||
# status check on your protected branches.
|
||||
|
||||
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:
|
||||
# issue_comment also fires on plain issues; skip those early.
|
||||
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]
|
||||
# Uncomment to tune:
|
||||
# excluded_approvers: dependabot[bot]
|
||||
# exempt_path_prefixes: docs/
|
||||
# protected_bases: main,release
|
||||
Loading…
x
Reference in New Issue
Block a user