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