feat(ci): agent workflows for PR fixes, fork review, and queue triage - #172
feat(ci): agent workflows for PR fixes, fork review, and queue triage#172defangdevs wants to merge 2 commits into
Conversation
|
Context and reasoning for this now live in #176 (self-maintaining repo tracker). |
Three GitHub Actions workflows that let the repo maintain itself, split by trust boundary rather than by task: - agent-pr.yml: own PRs, label-gated on `agent-fix`. Runs the flake checks relevant to the diff, regenerates modules/agent-box.nix on drift, fixes what is broken, pushes to the PR branch. Never touches master. - agent-review-fork.yml: fork PRs, which cannot use the above because a fork's `pull_request` gets no secrets. Uses `pull_request_target` but never checks out the PR head, holds only `pull-requests: write`, and runs with an explicit `--allowedTools` allowlist and no `--dangerously-skip-permissions` — so a prompt injection in the diff has no tool with which to egress the API key. Comment only; the checks are not run. - agent-triage.yml: weekly queue maintenance with five ordered rules, each of which removes a queue item or makes one routable. Closing an issue requires citing both the commit and the behaviour's current location — a `git log --grep` match alone false-positives (`#9` matches `#96`). Security effects: agent-review-fork.yml is the only workflow here reachable by an untrusted party, and the four properties that keep it safe are documented in its header. agent-triage.yml holds `contents: read`, so it structurally cannot push code. agent-pr.yml's `github.actor != 'github-actions[bot]'` guard is redundant while everything runs on GITHUB_TOKEN, and becomes load-bearing the moment any job here moves to a PAT or App identity. No AWS cost, IAM, or networking impact. Checks run: all three files validated as parsing, with triggers, permissions, concurrency groups, matrix and job guards confirmed to resolve as intended. The workflows themselves cannot run until merged (and `pull_request_target` only ever runs the base-branch copy). Requires one of CLAUDE_CODE_OAUTH_TOKEN or ANTHROPIC_API_KEY in Actions secrets; optionally CACHIX_AUTH_TOKEN + a CACHIX_CACHE var, without which the PR agent spends most of its budget rebuilding the nix store. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PsqGhkpjsb4kKAdVz25xKN
5129e80 to
acb3538
Compare
|
Rebased onto master ( Verified while rebasing:
One thing to weigh that the PR body doesn't cover: |
`if: >-` is a folded block scalar, so the nine explanatory `#` lines sitting
under it were folded into the expression as literal text instead of being
treated as comments. GitHub rejected the file outright — the run showed up as
a startup failure named after the raw path, with zero jobs, because it could
not parse far enough to read `name:`. Moved the commentary above the key and
said why it has to live there.
While in the file: pass the PR head branch to the push step through `env`
rather than interpolating it into the script. `${{ }}` inside `run:` is
substituted before the shell sees it, so a crafted branch name would execute;
same-repo-only limits that to people who already have write access, but the
fix costs one line. Both found by actionlint, which is now clean on all three
workflows apart from two informational SC2016 hits on intentionally
single-quoted printf formats.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SDHb5tyTqZwzKM6Qdr8cxv
|
Correction to my note above — I said the pins and safety properties checked out, and they do, but I should have run a linter before calling this review-ready: Cause: Second fix in the same push: the push step interpolated
Worth noting for review: none of these three workflows can be exercised before merge — |
Three workflows toward a self-maintaining repo, split by trust boundary rather than by task. Draft: none of them can run until merged, and the secrets below are not set yet.
What each does
agent-pr.ymlpull_request+agent-fixlabel, same-repo onlymodules/agent-box.nixon drift, fixes what is broken, pushes to the PR branchagent-review-fork.ymlpull_request_target, forks onlyagent-triage.ymlworkflow_dispatchLabel-gating the PR agent is deliberate: each run costs API tokens and ~10 min of runner time on the nix store, so running on every
synchronizewould burn both on PRs that are already green.Security effects
agent-review-fork.ymlis the only workflow here reachable by an untrusted party. It needspull_request_targetbecause a fork'spull_requestevent gets no secrets and therefore no API key. Four properties keep that safe, and they are documented in the file header because breaking any one of them is how repos get compromised:permissions:ispull-requests: writeand nothing else. Nocontents: write, noactions: write.--allowedTools "Read,Grep,Glob"and without--dangerously-skip-permissions. No Bash, no WebFetch — a diff instructing the model to exfiltrate$ANTHROPIC_API_KEYhas no tool with which to do it. This is the opposite choice fromagent-pr.yml, and the reason is that here the input is attacker-controlled.GH_TOKENis absent from the agent step's environment; the diff is fetched before and the comment posted after.The diff is wrapped in
UNTRUSTED-DIFFmarkers with an instruction that injection attempts are findings to report, not instructions to follow.agent-triage.ymlholdscontents: read, so it structurally cannot push code.agent-pr.yml'sgithub.actor != 'github-actions[bot]'guard is redundant today —GITHUB_TOKEN-authored events do not trigger workflows — and becomes the only thing preventing a self-review cascade the moment any job here moves to a PAT or GitHub App identity. That moment is coming: fixing the AMI publish gap needs an elevated identity. Keep the agent loop onGITHUB_TOKENand give only the publisher the elevated one.Why closing an issue requires two pieces of evidence
Triage rule 1 will not close an issue on a
git log --grepmatch alone, because that match false-positives: grepping for#9hits commits referencing#96and#94. It must cite both a commit SHA and the behaviour's current location in the tree, or it drops to rule 4 and writes a concrete next step instead.The rules have real debt to aim at right now: 7 of 29 open issues carry no label at all (#170, #167, #159, #154, #135, #127, #126), and two of three open PRs are parked
[backup]drafts untouched since 28 Jul (#147, #148). Rule 3 comments on those and labels themstalebut deliberately does not close them — you cannot know what a backup is protecting. #103 was the first rule-1 candidate and has already been closed by hand with its citation.Checks run
All three files validated as parsing, with triggers,
permissions, concurrency groups, the review matrix, and the job-levelif:guards confirmed to resolve as intended. The workflows cannot execute until merged, andpull_request_targetonly ever runs the base-branch copy of its file — so behaviour is unverified until this lands. That is the main reason it is a draft.No AWS cost, IAM, or networking impact.
Before merging — needs from a human
CLAUDE_CODE_OAUTH_TOKEN(subscription auth, unmetered) orANTHROPIC_API_KEYunder Settings → Secrets and variables → Actions. Both set is an auth error.CACHIX_AUTH_TOKEN+ aCACHIX_CACHErepo var. The step skips cleanly without them, but with no warm store the PR agent spends most of its 30-minute budget rebuilding the VM closure rather than thinking. This is the single biggest determinant of whetheragent-pr.ymlis useful on this repo.@anthropic-ai/claude-code@2.1.0pin is the version you want — pinned rather than@latestso the harness does not change under the job, but not verified against the registry.agent-fix,agent-triaged,stale,needs-decision. None exist yet.🤖 Generated with Claude Code
https://claude.ai/code/session_01PsqGhkpjsb4kKAdVz25xKN