diff --git a/.github/actions/issue-auto-implement/AGENTS.md b/.github/actions/issue-auto-implement/AGENTS.md index 0e58571..5e16475 100644 --- a/.github/actions/issue-auto-implement/AGENTS.md +++ b/.github/actions/issue-auto-implement/AGENTS.md @@ -51,7 +51,12 @@ From the workflow event payload, derive: ## Restricting who can trigger -Only members of the `github_allowed_trigger_team` (input; set via repo variable `AUTO_IMPLEMENT_ALLOWED_TRIGGER_TEAM`) may trigger the flow. First step checks `github.actor` against that team; if the variable is unset or the actor is not a member, fail immediately. Token must have `read:org`. +The first step enforces one of two gates (exactly one must be configured; no bypass): + +1. **Permission check** — If `github_allowed_trigger_min_permission` is set (repo variable `AUTO_IMPLEMENT_ALLOWED_TRIGGER_MIN_PERMISSION`: `triage`, `push`, `maintain`, or `admin`), the action calls the repo collaborator permission API and requires the actor to have at least that permission. Works with the default `GITHUB_TOKEN`. +2. **Team check** — Otherwise, if `github_allowed_trigger_team` is set (repo variable `AUTO_IMPLEMENT_ALLOWED_TRIGGER_TEAM`, e.g. `org/team`), the action checks `github.actor` against that team; if unset or not a member, fail. Token must have `read:org` (PAT if `GITHUB_TOKEN` lacks it). + +If neither variable is set, the step fails. When both are set, the permission check is used and the team value is ignored. ## Labels diff --git a/.github/actions/issue-auto-implement/README.md b/.github/actions/issue-auto-implement/README.md index e9c9145..cb9f6f7 100644 --- a/.github/actions/issue-auto-implement/README.md +++ b/.github/actions/issue-auto-implement/README.md @@ -5,28 +5,31 @@ Reusable composite action for label-triggered issue automation: assess (request ## How to use (quick start) 1. **Workflow** — Ensure `.github/workflows/issue-auto-implement.yml` exists and calls this action (see the workflow in this repo for the exact `on:` and `uses:`). -2. **Secrets and variable** — In the repo: Settings → Secrets and variables → Actions. Add secret **`AUTO_IMPLEMENT_ANTHROPIC_API_KEY`** (Anthropic API key). Add variable **`AUTO_IMPLEMENT_ALLOWED_TRIGGER_TEAM`** (e.g. `your-org/your-team`); only members of this team can trigger the action. +2. **Secrets and variables** — In the repo: Settings → Secrets and variables → Actions. Add secret **`AUTO_IMPLEMENT_ANTHROPIC_API_KEY`** (Anthropic API key). For who can trigger, set **one** of: **`AUTO_IMPLEMENT_ALLOWED_TRIGGER_MIN_PERMISSION`** (e.g. `push` or `maintain`; works with default token) or **`AUTO_IMPLEMENT_ALLOWED_TRIGGER_TEAM`** (e.g. `org/team`; token needs `read:org`). 3. **Trigger label** — Create the labels once so you can add them to issues. Either run the **Issue auto-implement setup** workflow (Actions → Issue auto-implement setup → Run workflow), which creates `automation/auto-implement`, `automation/needs-info`, and `automation/pr-created`; or create the trigger label **`automation/auto-implement`** manually in the repo (Settings or Issues → Labels). The main action also ensures these labels exist when it runs, but the trigger label must exist before you can add it to an issue. 4. **Trigger** — On an issue, add the label `automation/auto-implement`. The workflow runs: it assesses the issue (request more info vs implement), and if implement, runs the Claude Code CLI and opens a PR. You can also comment on the issue (to add context and re-trigger) or review the PR (to iterate). ## Usage (reference) -Used by `.github/workflows/issue-auto-implement.yml`. Requires `anthropic_api_key` (e.g. from repo secret `AUTO_IMPLEMENT_ANTHROPIC_API_KEY`), `github_allowed_trigger_team` (e.g. from repo variable `AUTO_IMPLEMENT_ALLOWED_TRIGGER_TEAM`), and `github_token` from the workflow. +Used by `.github/workflows/issue-auto-implement.yml`. Requires `anthropic_api_key` (e.g. from repo secret `AUTO_IMPLEMENT_ANTHROPIC_API_KEY`), one of `github_allowed_trigger_min_permission` or `github_allowed_trigger_team` (repo variables), and `github_token` from the workflow. ## Inputs | Input | Required | Default | Description | |-------|----------|---------|-------------| | `anthropic_api_key` | Yes | - | Claude API key. Set via repo secret `AUTO_IMPLEMENT_ANTHROPIC_API_KEY` so multiple actions can use different keys. | -| `github_token` | Yes | - | Token with contents, issues, pull-requests, read:org | +| `github_token` | Yes | - | Token (contents, issues, pull-requests; read:org only if using team check) | | `context_files` | No | AGENTS.md,REFERENCE.md | Comma-separated paths for assessment context | | `assessment_reference_issue` | No | 192 | Reference issue number for "enough information" | | `label_prefix` | No | automation | Prefix for labels (e.g. automation/auto-implement) | | `verify_commands` | No | go test ./... | Commands run for verification | | `max_implement_retries` | No | 3 | Max retries on verify failure (cap 5) | -| `github_allowed_trigger_team` | Yes | - | GitHub Team slug (e.g. org/team); only members can trigger. Set via repo variable `AUTO_IMPLEMENT_ALLOWED_TRIGGER_TEAM`. | +| `github_allowed_trigger_team` | No* | - | Team slug (e.g. org/team); only members can trigger. Repo variable `AUTO_IMPLEMENT_ALLOWED_TRIGGER_TEAM`. Ignored if min_permission is set. Token needs read:org. | +| `github_allowed_trigger_min_permission` | No* | - | Require actor has at least this repo permission: triage, push, maintain, or admin. Repo variable `AUTO_IMPLEMENT_ALLOWED_TRIGGER_MIN_PERMISSION`. Works with default GITHUB_TOKEN. | | `post_pr_comment` | No | false | When true, post a comment on the issue linking to the new PR when one is created. | +*One of `github_allowed_trigger_min_permission` or `github_allowed_trigger_team` must be set (via repo variables). + Secrets and variables use an action-specific prefix (e.g. `AUTO_IMPLEMENT_`) so each action can have its own keys/variables and it's clear which workflow uses which. This also avoids clashing with platform-reserved names (e.g. `GITHUB_*`). ## CI/CD: what you need to run this workflow @@ -35,8 +38,10 @@ To use this action in GitHub Actions: 1. **Workflow** — Call the action from a workflow (e.g. `.github/workflows/issue-auto-implement.yml`) on `issues.labeled`, `issue_comment`, `pull_request_review`, and/or `pull_request_review_comment`. The job needs `contents: write`, `issues: write`, `pull-requests: write`. 2. **Secrets** — Add **`AUTO_IMPLEMENT_ANTHROPIC_API_KEY`** (repo secret). Used for the assess step and passed to the Claude Code CLI in the implement step. -3. **Variables** — Add **`AUTO_IMPLEMENT_ALLOWED_TRIGGER_TEAM`** (repo variable, required): GitHub Team slug (e.g. `org/team-name`) whose members may trigger the run. -4. **Token** — Pass `github_token` (e.g. `secrets.GITHUB_TOKEN`) to the action. The team check needs `read:org`; if `GITHUB_TOKEN` lacks it, use a PAT with `read:org` and pass it as the token. +3. **Variables (trigger gate)** — Set **one** of: + - **`AUTO_IMPLEMENT_ALLOWED_TRIGGER_MIN_PERMISSION`** (repo variable): `triage`, `push`, `maintain`, or `admin`. Only users with at least this repo permission can trigger. Works with default `GITHUB_TOKEN`. + - **`AUTO_IMPLEMENT_ALLOWED_TRIGGER_TEAM`** (repo variable): org/team slug (e.g. `org/team-name`). Only team members can trigger. Token must have `read:org` (use a PAT if `GITHUB_TOKEN` lacks it). +4. **Token** — Pass `github_token` (e.g. `secrets.GITHUB_TOKEN`). If using the team check, the token needs `read:org`; the permission check works with the default token. 5. **Implement in CI** — The action installs the Claude Code CLI (`@anthropic-ai/claude-code`) when the assess outcome is `implement`, so the workflow does not need to install it. Implement runs in the repo with Read/Edit/Bash; the CLI uses `AUTO_IMPLEMENT_ANTHROPIC_API_KEY`. No other setup is required. Optionally set `verify_commands` (default `go test ./...`) and `context_files` (default `AGENTS.md,REFERENCE.md`) to match your repo. @@ -44,8 +49,9 @@ No other setup is required. Optionally set `verify_commands` (default `go test . ## Secrets and variables (repo setup) - **`AUTO_IMPLEMENT_ANTHROPIC_API_KEY`** (repo secret) — Claude API key for the assess and implement steps. Add under Settings → Secrets and variables → Actions. -- **`AUTO_IMPLEMENT_ALLOWED_TRIGGER_TEAM`** (repo variable, required) — GitHub Team slug (e.g. `org/team-name`) whose members may trigger the workflow. Add under Settings → Secrets and variables → Actions. The first step checks `github.actor` against this team; if unset or not a member, the run fails. -- **Token for team check** — The workflow passes `github_token` (usually `secrets.GITHUB_TOKEN`) to the action. The team check needs `read:org`. If your default `GITHUB_TOKEN` does not have `read:org`, use a PAT with that scope and pass it as the token (e.g. a repo secret) instead. +- **Trigger gate (set one):** + - **`AUTO_IMPLEMENT_ALLOWED_TRIGGER_MIN_PERMISSION`** (repo variable) — Require the triggering user to have at least this repo permission: `triage`, `push`, `maintain`, or `admin`. Works with the default `GITHUB_TOKEN`. Add under Settings → Secrets and variables → Actions → Variables. + - **`AUTO_IMPLEMENT_ALLOWED_TRIGGER_TEAM`** (repo variable) — GitHub Team slug (e.g. `org/team-name`) whose members may trigger. The first step checks `github.actor` against this team. The token needs `read:org`; if `GITHUB_TOKEN` lacks it, use a PAT and pass it as `github_token`. ## Triggers diff --git a/.github/actions/issue-auto-implement/action.yml b/.github/actions/issue-auto-implement/action.yml index c1ac747..8fd056c 100644 --- a/.github/actions/issue-auto-implement/action.yml +++ b/.github/actions/issue-auto-implement/action.yml @@ -28,8 +28,11 @@ inputs: required: false default: '3' github_allowed_trigger_team: - description: 'GitHub Team slug whose members may trigger the flow (e.g. hookdeck/automation-triggers). Set via repo variable AUTO_IMPLEMENT_ALLOWED_TRIGGER_TEAM.' - required: true + description: 'GitHub Team slug whose members may trigger (e.g. org/team). Requires token with read:org. Set via repo variable AUTO_IMPLEMENT_ALLOWED_TRIGGER_TEAM. Ignored if github_allowed_trigger_min_permission is set.' + required: false + github_allowed_trigger_min_permission: + description: 'Alternative to team check: require actor has at least this repo permission (triage, push, maintain, admin). Works with default GITHUB_TOKEN. Set via repo variable AUTO_IMPLEMENT_ALLOWED_TRIGGER_MIN_PERMISSION.' + required: false post_pr_comment: description: 'When true, post a comment on the issue linking to the new PR when one is created' required: false @@ -37,25 +40,59 @@ inputs: runs: using: 'composite' steps: - - name: Check allowed trigger team + - name: Check allowed trigger (permission or team) shell: bash + env: + GITHUB_TOKEN: ${{ inputs.github_token }} + REPO: ${{ github.repository }} + ACTOR: ${{ github.actor }} + MIN_PERM: ${{ inputs.github_allowed_trigger_min_permission }} + TEAM: ${{ inputs.github_allowed_trigger_team }} run: | - TEAM="${{ inputs.github_allowed_trigger_team }}" + # Option A: Require minimum repo permission (works with default GITHUB_TOKEN, no read:org) + if [ -n "$MIN_PERM" ]; then + case "$MIN_PERM" in + triage|push|maintain|admin) ;; + *) echo "::error::github_allowed_trigger_min_permission must be one of: triage, push, maintain, admin"; exit 1 ;; + esac + RESP=$(curl -s -w "\n%{http_code}" \ + -H "Authorization: Bearer $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/$REPO/collaborators/$ACTOR/permission") + HTTP=$(echo "$RESP" | tail -n1) + BODY=$(echo "$RESP" | sed '$d') + if [ "$HTTP" != "200" ]; then + echo "::error::Actor $ACTOR has no collaborator permission on this repo (HTTP $HTTP). Only users with at least $MIN_PERM can trigger." + exit 1 + fi + PERM=$(echo "$BODY" | jq -r '.permission // "read"') + # Order: read/pull < triage < push < maintain < admin + level() { case "$1" in read|pull) echo 1;; triage) echo 2;; push) echo 3;; maintain) echo 4;; admin) echo 5;; *) echo 0;; esac; } + ACTOR_LEVEL=$(level "$PERM") + MIN_LEVEL=$(level "$MIN_PERM") + if [ "$ACTOR_LEVEL" -lt "$MIN_LEVEL" ]; then + echo "::error::Actor $ACTOR has permission $PERM; at least $MIN_PERM is required to trigger this workflow." + exit 1 + fi + echo "Actor $ACTOR has permission $PERM (>= $MIN_PERM)." + exit 0 + fi + # Option B: Require team membership (token needs read:org) if [ -z "$TEAM" ]; then - echo "::error::github_allowed_trigger_team must be set (e.g. repo variable AUTO_IMPLEMENT_ALLOWED_TRIGGER_TEAM). Only team members can trigger this workflow." + echo "::error::Set one of: repo variable AUTO_IMPLEMENT_ALLOWED_TRIGGER_MIN_PERMISSION (e.g. push) or AUTO_IMPLEMENT_ALLOWED_TRIGGER_TEAM (e.g. org/team)." exit 1 fi ORG="${TEAM%/*}" SLUG="${TEAM#*/}" HTTP=$(curl -s -o /dev/null -w "%{http_code}" \ - -H "Authorization: Bearer ${{ inputs.github_token }}" \ + -H "Authorization: Bearer $GITHUB_TOKEN" \ -H "Accept: application/vnd.github+json" \ - "https://api.github.com/orgs/${ORG}/teams/${SLUG}/members/${{ github.actor }}") + "https://api.github.com/orgs/${ORG}/teams/${SLUG}/members/$ACTOR") if [ "$HTTP" != "204" ]; then - echo "::error::Actor ${{ github.actor }} is not in team $TEAM (HTTP $HTTP). Only team members can trigger this workflow." + echo "::error::Actor $ACTOR is not in team $TEAM (HTTP $HTTP). Only team members can trigger. Token may need read:org." exit 1 fi - echo "Actor ${{ github.actor }} is in team $TEAM." + echo "Actor $ACTOR is in team $TEAM." - name: Ensure labels exist shell: bash env: diff --git a/.github/workflows/issue-auto-implement.yml b/.github/workflows/issue-auto-implement.yml index cf6f974..343ae38 100644 --- a/.github/workflows/issue-auto-implement.yml +++ b/.github/workflows/issue-auto-implement.yml @@ -1,6 +1,6 @@ # Label-triggered issue automation: assess, implement-verify loop, create PR or iterate on PR. # Triggers: issue labeled (automation/auto-implement), issue comment, PR review. -# Require repo variable AUTO_IMPLEMENT_ALLOWED_TRIGGER_TEAM (org/team slug). Token may need read:org (PAT) for team check. +# Gate: set one of AUTO_IMPLEMENT_ALLOWED_TRIGGER_MIN_PERMISSION (e.g. push; works with default token) or AUTO_IMPLEMENT_ALLOWED_TRIGGER_TEAM (org/team; token needs read:org). name: Issue auto-implement on: @@ -26,7 +26,7 @@ jobs: contents: write issues: write pull-requests: write - # Team check requires read:org; use a PAT as github_token if GITHUB_TOKEN lacks it + # read:org only needed if using team check (AUTO_IMPLEMENT_ALLOWED_TRIGGER_TEAM) steps: - name: Checkout uses: actions/checkout@v4 @@ -37,4 +37,5 @@ jobs: with: anthropic_api_key: ${{ secrets.AUTO_IMPLEMENT_ANTHROPIC_API_KEY }} github_token: ${{ secrets.GITHUB_TOKEN }} + github_allowed_trigger_min_permission: ${{ vars.AUTO_IMPLEMENT_ALLOWED_TRIGGER_MIN_PERMISSION }} github_allowed_trigger_team: ${{ vars.AUTO_IMPLEMENT_ALLOWED_TRIGGER_TEAM }}