Skip to content

Commit e3cb573

Browse files
authored
feat(ci): add permission-based trigger gate to issue-auto-implement GitHub Action (#242)
* feat(ci): add issue auto-implement action and workflow - Add workflow issue-auto-implement.yml (label, comment, PR review triggers) - Add composite action: team check, labels, assess script, implement placeholder, verify loop, create PR - Add assess script (TypeScript/tsx, Claude, fixtures) with vitest tests - Add workflow issue-auto-implement-test.yml to run assess tests on PR - Add README (usage, inputs, testing) and AGENTS.md (flows, verification, next steps) - Use AUTO_IMPLEMENT_* prefix for secrets/vars; require github_allowed_trigger_team Made-with: Cursor * feat(ci): pass context_files into assess and include in Claude prompt - Add CONTEXT_FILES and GITHUB_WORKSPACE to assess step env - Load repo files (AGENTS.md, REFERENCE.md, etc.) and append to assessment prompt - Skip missing files; REPO_ROOT from GITHUB_WORKSPACE or cwd for local runs Made-with: Cursor * feat(ci): fetch all issue comments for assessment - For issues and issue_comment events, call GitHub API for issue comments - Include all comments in Claude prompt (author, date, body) - Fall back to payload comment/comments when API not used Made-with: Cursor * fix(ci): stabilize issue-auto-implement assess tests workflow - Set GITHUB_WORKSPACE in job env so assess script has repo root in CI - Use working-directory instead of cd for install/test step - Add npm cache keyed by assess package-lock.json Made-with: Cursor * fix(ci): do not run main() when assess script is loaded by Vitest In CI the runner sets GITHUB_EVENT_PATH, so the script was invoking main() on import; loadPayload() then threw and process.exit(1) caused Vitest to fail with an unhandled error. Only run main() when GITHUB_EVENT_PATH is set and VITEST is not (i.e. when invoked as script, not by tests). Made-with: Cursor * feat(ci): wire Claude implement step and commit-and-push - Add assess/implement.ts: fetch issue, call Claude for edits JSON, apply files, write commit message - Action: run implement script with env; commit and push using generated commit message - Add .gitignore for .commit_msg; document implement script in AGENTS.md Made-with: Cursor * feat(ci): true implement-verify loop with PREVIOUS_VERIFY_OUTPUT - Implement script accepts PREVIOUS_VERIFY_OUTPUT and adds it to Claude prompt on retry - Single step loops: implement → commit/push → verify; on verify failure retry with output - Create PR uses implement_verify_loop.outcome; document loop in AGENTS.md Made-with: Cursor * refactor(issue-auto-implement): move assess into src/ and test/ - Main code: assess/src/ (index.ts, implement.ts, normalize.ts) - Tests: assess/test/ (index.test.ts, normalize.test.ts) - Fixtures stay at assess/fixtures/ - Update package.json scripts and action.yml to use src/ - Add .env.example, setup-local-env.sh, .gitignore .env; update README/AGENTS paths Made-with: Cursor * refactor(issue-auto-implement): consolidate tests under test/ (unit, integration, fixtures) - test/unit/ — unit tests (npm test), no API - test/integration/ — Claude API tests (npm run test:integration), not in CI - test/fixtures/ — shared event JSONs for both and assess:fixture script - Remove top-level fixtures/ and test-integration/; update vitest configs and paths Made-with: Cursor * chore: ignore auto-implement worktrees Made-with: Cursor * fix(issue-auto-implement): use Claude Code CLI only, single API key - Implement runs only Claude Code CLI (no API path); pass AUTO_IMPLEMENT_ANTHROPIC_API_KEY to CLI - Assess uses only AUTO_IMPLEMENT_ANTHROPIC_API_KEY - Action installs Claude Code CLI in CI when assess outcome is implement - README: add CI/CD checklist; docs for implement and env - AGENTS.md: implement flow and backlog updated Made-with: Cursor * fix(issue-auto-implement): create new PR when previous closed; TS push-and-open-pr - push-and-open-pr.ts: check gh pr view --json state; only skip create when OPEN - run-local-assess.ts: worktree flow, calls pushAndOpenPr, default context - load-dotenv.ts: used by run-local-assess - scripts/push-and-open-pr.sh: optional standalone script Made-with: Cursor * docs(issue-auto-implement): quick start, remove TEST_PLAN refs, add assess:local - README: add How to use (workflow, secrets, trigger label, trigger) - Remove TEST_PLAN.md references from README and AGENTS.md - package.json: add assess:local script Made-with: Cursor * feat(issue-auto-implement): add setup workflow to create labels - Add issue-auto-implement-setup.yml (workflow_dispatch) to create automation/auto-implement, automation/needs-info, automation/pr-created - README: quick start step 3 mentions running setup workflow - REMOTE_TEST_PLAN: prerequisites reference setup workflow Made-with: Cursor * chore: remove REMOTE_TEST_PLAN.md from source control Made-with: Cursor * feat(issue-auto-implement): add permission-based trigger gate (works with default token) - Add optional github_allowed_trigger_min_permission input (triage, push, maintain, admin). When set via repo variable AUTO_IMPLEMENT_ALLOWED_TRIGGER_MIN_PERMISSION, the action checks repo collaborator permission via API; works with default GITHUB_TOKEN (no read:org). - Keep team check (AUTO_IMPLEMENT_ALLOWED_TRIGGER_TEAM) as alternative; token needs read:org. - Exactly one gate required per run; if both variables set, permission check is used. - Update README and AGENTS.md with both options. Made-with: Cursor
1 parent 1923327 commit e3cb573

4 files changed

Lines changed: 69 additions & 20 deletions

File tree

.github/actions/issue-auto-implement/AGENTS.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,12 @@ From the workflow event payload, derive:
5151

5252
## Restricting who can trigger
5353

54-
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`.
54+
The first step enforces one of two gates (exactly one must be configured; no bypass):
55+
56+
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`.
57+
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).
58+
59+
If neither variable is set, the step fails. When both are set, the permission check is used and the team value is ignored.
5560

5661
## Labels
5762

.github/actions/issue-auto-implement/README.md

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,28 +5,31 @@ Reusable composite action for label-triggered issue automation: assess (request
55
## How to use (quick start)
66

77
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:`).
8-
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.
8+
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`).
99
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.
1010
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).
1111

1212
## Usage (reference)
1313

14-
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.
14+
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.
1515

1616
## Inputs
1717

1818
| Input | Required | Default | Description |
1919
|-------|----------|---------|-------------|
2020
| `anthropic_api_key` | Yes | - | Claude API key. Set via repo secret `AUTO_IMPLEMENT_ANTHROPIC_API_KEY` so multiple actions can use different keys. |
21-
| `github_token` | Yes | - | Token with contents, issues, pull-requests, read:org |
21+
| `github_token` | Yes | - | Token (contents, issues, pull-requests; read:org only if using team check) |
2222
| `context_files` | No | AGENTS.md,REFERENCE.md | Comma-separated paths for assessment context |
2323
| `assessment_reference_issue` | No | 192 | Reference issue number for "enough information" |
2424
| `label_prefix` | No | automation | Prefix for labels (e.g. automation/auto-implement) |
2525
| `verify_commands` | No | go test ./... | Commands run for verification |
2626
| `max_implement_retries` | No | 3 | Max retries on verify failure (cap 5) |
27-
| `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`. |
27+
| `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. |
28+
| `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. |
2829
| `post_pr_comment` | No | false | When true, post a comment on the issue linking to the new PR when one is created. |
2930

31+
*One of `github_allowed_trigger_min_permission` or `github_allowed_trigger_team` must be set (via repo variables).
32+
3033
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_*`).
3134

3235
## CI/CD: what you need to run this workflow
@@ -35,17 +38,20 @@ To use this action in GitHub Actions:
3538

3639
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`.
3740
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.
38-
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.
39-
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.
41+
3. **Variables (trigger gate)** — Set **one** of:
42+
- **`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`.
43+
- **`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).
44+
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.
4045
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`.
4146

4247
No other setup is required. Optionally set `verify_commands` (default `go test ./...`) and `context_files` (default `AGENTS.md,REFERENCE.md`) to match your repo.
4348

4449
## Secrets and variables (repo setup)
4550

4651
- **`AUTO_IMPLEMENT_ANTHROPIC_API_KEY`** (repo secret) — Claude API key for the assess and implement steps. Add under Settings → Secrets and variables → Actions.
47-
- **`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.
48-
- **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.
52+
- **Trigger gate (set one):**
53+
- **`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.
54+
- **`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`.
4955

5056
## Triggers
5157

.github/actions/issue-auto-implement/action.yml

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,34 +28,71 @@ inputs:
2828
required: false
2929
default: '3'
3030
github_allowed_trigger_team:
31-
description: 'GitHub Team slug whose members may trigger the flow (e.g. hookdeck/automation-triggers). Set via repo variable AUTO_IMPLEMENT_ALLOWED_TRIGGER_TEAM.'
32-
required: true
31+
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.'
32+
required: false
33+
github_allowed_trigger_min_permission:
34+
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.'
35+
required: false
3336
post_pr_comment:
3437
description: 'When true, post a comment on the issue linking to the new PR when one is created'
3538
required: false
3639
default: 'false'
3740
runs:
3841
using: 'composite'
3942
steps:
40-
- name: Check allowed trigger team
43+
- name: Check allowed trigger (permission or team)
4144
shell: bash
45+
env:
46+
GITHUB_TOKEN: ${{ inputs.github_token }}
47+
REPO: ${{ github.repository }}
48+
ACTOR: ${{ github.actor }}
49+
MIN_PERM: ${{ inputs.github_allowed_trigger_min_permission }}
50+
TEAM: ${{ inputs.github_allowed_trigger_team }}
4251
run: |
43-
TEAM="${{ inputs.github_allowed_trigger_team }}"
52+
# Option A: Require minimum repo permission (works with default GITHUB_TOKEN, no read:org)
53+
if [ -n "$MIN_PERM" ]; then
54+
case "$MIN_PERM" in
55+
triage|push|maintain|admin) ;;
56+
*) echo "::error::github_allowed_trigger_min_permission must be one of: triage, push, maintain, admin"; exit 1 ;;
57+
esac
58+
RESP=$(curl -s -w "\n%{http_code}" \
59+
-H "Authorization: Bearer $GITHUB_TOKEN" \
60+
-H "Accept: application/vnd.github+json" \
61+
"https://api.github.com/repos/$REPO/collaborators/$ACTOR/permission")
62+
HTTP=$(echo "$RESP" | tail -n1)
63+
BODY=$(echo "$RESP" | sed '$d')
64+
if [ "$HTTP" != "200" ]; then
65+
echo "::error::Actor $ACTOR has no collaborator permission on this repo (HTTP $HTTP). Only users with at least $MIN_PERM can trigger."
66+
exit 1
67+
fi
68+
PERM=$(echo "$BODY" | jq -r '.permission // "read"')
69+
# Order: read/pull < triage < push < maintain < admin
70+
level() { case "$1" in read|pull) echo 1;; triage) echo 2;; push) echo 3;; maintain) echo 4;; admin) echo 5;; *) echo 0;; esac; }
71+
ACTOR_LEVEL=$(level "$PERM")
72+
MIN_LEVEL=$(level "$MIN_PERM")
73+
if [ "$ACTOR_LEVEL" -lt "$MIN_LEVEL" ]; then
74+
echo "::error::Actor $ACTOR has permission $PERM; at least $MIN_PERM is required to trigger this workflow."
75+
exit 1
76+
fi
77+
echo "Actor $ACTOR has permission $PERM (>= $MIN_PERM)."
78+
exit 0
79+
fi
80+
# Option B: Require team membership (token needs read:org)
4481
if [ -z "$TEAM" ]; then
45-
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."
82+
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)."
4683
exit 1
4784
fi
4885
ORG="${TEAM%/*}"
4986
SLUG="${TEAM#*/}"
5087
HTTP=$(curl -s -o /dev/null -w "%{http_code}" \
51-
-H "Authorization: Bearer ${{ inputs.github_token }}" \
88+
-H "Authorization: Bearer $GITHUB_TOKEN" \
5289
-H "Accept: application/vnd.github+json" \
53-
"https://api.github.com/orgs/${ORG}/teams/${SLUG}/members/${{ github.actor }}")
90+
"https://api.github.com/orgs/${ORG}/teams/${SLUG}/members/$ACTOR")
5491
if [ "$HTTP" != "204" ]; then
55-
echo "::error::Actor ${{ github.actor }} is not in team $TEAM (HTTP $HTTP). Only team members can trigger this workflow."
92+
echo "::error::Actor $ACTOR is not in team $TEAM (HTTP $HTTP). Only team members can trigger. Token may need read:org."
5693
exit 1
5794
fi
58-
echo "Actor ${{ github.actor }} is in team $TEAM."
95+
echo "Actor $ACTOR is in team $TEAM."
5996
- name: Ensure labels exist
6097
shell: bash
6198
env:

.github/workflows/issue-auto-implement.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Label-triggered issue automation: assess, implement-verify loop, create PR or iterate on PR.
22
# Triggers: issue labeled (automation/auto-implement), issue comment, PR review.
3-
# Require repo variable AUTO_IMPLEMENT_ALLOWED_TRIGGER_TEAM (org/team slug). Token may need read:org (PAT) for team check.
3+
# 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).
44
name: Issue auto-implement
55

66
on:
@@ -26,7 +26,7 @@ jobs:
2626
contents: write
2727
issues: write
2828
pull-requests: write
29-
# Team check requires read:org; use a PAT as github_token if GITHUB_TOKEN lacks it
29+
# read:org only needed if using team check (AUTO_IMPLEMENT_ALLOWED_TRIGGER_TEAM)
3030
steps:
3131
- name: Checkout
3232
uses: actions/checkout@v4
@@ -37,4 +37,5 @@ jobs:
3737
with:
3838
anthropic_api_key: ${{ secrets.AUTO_IMPLEMENT_ANTHROPIC_API_KEY }}
3939
github_token: ${{ secrets.GITHUB_TOKEN }}
40+
github_allowed_trigger_min_permission: ${{ vars.AUTO_IMPLEMENT_ALLOWED_TRIGGER_MIN_PERMISSION }}
4041
github_allowed_trigger_team: ${{ vars.AUTO_IMPLEMENT_ALLOWED_TRIGGER_TEAM }}

0 commit comments

Comments
 (0)