ci: unblock bot-triage for read-permission contributors - #2587
Conversation
The `anthropics/claude-code-action@v1` action defaults to rejecting actors without write permission to the repo — a sensible default for PR-related automation, but exactly backward for issue triage on a public OSS bug tracker, where contributors filing bugs are precisely who we want responded to. Symptom: 5 of the last 13 bot-triage runs failed at the "Run Triage" step with `Actor does not have write permissions to the repository`, producing zero triage comments on issues opened by external contributors. Fix: - `allowed_non_write_users: '*'` — the action input designed for this exact case. Docs name "issue labeling" as the canonical example. - Added `workflow_dispatch` with `issue_number` input so missed issues can be re-triaged without close/reopen. - Hardened the `if:` guard so the wheels-bot self-loop check no longer evaluates `github.event.issue` on dispatch events. Safe because the workflow runs with `permissions: contents: read`, posts via an App token independent of the actor, and `claude_args` restricts the model's shell to `gh` + read-only `git` + file reads. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Wheels Bot — Reviewer A
TL;DR: This is a clean, minimal CI fix for a confirmed production problem (5 missed triage runs from read-permission contributors). All three changes -- allowed_non_write_users: '*', workflow_dispatch recovery trigger, and the hardened if: guard -- are correct and well-justified. I have one minor observation about the issue_number input type, but it does not block approval.
Correctness
if: guard (line 27): The refactor from a bare github.event.issue.user.login check to the event-gated form is correct:
&& (github.event_name != 'issues' || github.event.issue.user.login != 'wheels-bot[bot]')On workflow_dispatch, github.event_name != 'issues' is truthy, so the bot-login guard is skipped -- which is safe because the idempotency marker check in the gate step still fires before Run Triage proceeds.
Concurrency group (line 17): github.event.issue.number || inputs.issue_number correctly handles both trigger paths. On workflow_dispatch, github.event.issue.number is empty/undefined and GitHub Actions evaluates it as falsy, falling through to inputs.issue_number.
Minor observation -- issue_number input type: The dispatch input is declared type: string and interpolated directly into the prompt:
prompt: |
/triage-issue ${{ github.event.issue.number || inputs.issue_number }}A malformed value passed via gh workflow run -f issue_number=... (e.g. with embedded newlines) would inject additional content into the prompt. The blast radius is small -- tool restrictions limit the model shell; triggering dispatch requires write access; the model already reads untrusted issue content -- but declaring the input as type: number if the action schema permits it would close the gap at zero cost. Worth a follow-up; not a blocker.
Security
allowed_non_write_users: '*' (line 68): The PR's three-part safety argument is sound:
- Workflow-level
permissions: contents: readrestricts the default GITHUB_TOKEN. - Comments are posted via the GitHub App token (independent of the actor's repo permissions).
claude_argslocks the model's shell togh:*+ read-only git +Read/Grep/Glob-- noWrite, noEdit, no unconstrainedBash.
The worst-case prompt-injection outcome (a misleading triage comment under the bot's name) is recoverable, bounded, and not materially different from the risk that existed before this PR (write-permission users were already sending untrusted issue content into the model). The action's own docs name "issue labeling" as the canonical use case for this input -- this is the right tool.
Commits
Single commit: ci: unblock bot-triage for read-permission contributors -- valid type, no scope (fine), 52-char subject, not ALL-CAPS. Conforms to commitlint.config.js.
Docs / Tests
No CFML code changed; cross-engine and convention checklists are N/A. The PR's test plan (dispatch a missed issue after merge, watch the next external issue) is the appropriate verification strategy for a CI workflow fix -- no automated test coverage is expected or missing here.
Wheels Bot — Reviewer B (round 1)A's review is substantive and accurate. The approval is backed by concrete line-by-line analysis of a small, clean diff. One minor false positive in A's follow-up suggestion, but it does not affect the verdict. Converging on approve. SycophancyNone detected. A cited specific lines, explained the False positives
Everything else A checked — the Missed issuesNone detected. The diff touches only Verdict alignmentA's ConvergenceAligned on |
Summary
The wheels-bot triage workflow has been silently failing for issues opened by external contributors (read-permission users). 5 of the last 13
bot-triageruns ended infailureat the Run Triage step withActor does not have write permissions to the repository.Pattern is clean: every recent failure was an issue from
@zainforbjs(read perms); every success was an issue from@bpamiri(write/admin). Theanthropics/claude-code-action@v1defaults to rejecting non-write actors — sensible for PR automation, exactly backward for issue triage on a public OSS bug tracker.What changed
allowed_non_write_users: '*'on theRun Triagestep — the action input designed for this exact case. Its docs name "issue labeling" as the canonical example.workflow_dispatchtrigger withissue_numberinput — so the 5 missed issues (and any future misses) can be re-triaged without the close/reopen dance.if:guard hardened so the wheels-bot self-loop check doesn't try to evaluategithub.event.issueon dispatch events.Why it's safe
The override is scoped narrowly to the triage workflow:
permissions: contents: read— the action cannot mutate code.claude_argsrestricts the model's shell toBash(gh:*)+ read-onlygit+Read/Grep/Glob. NoWrite, noEdit, no generalBash.Worst-case under prompt injection: a misleading triage comment under the bot's name — recoverable, bounded, and we're already reading the same untrusted content into the model today.
Why only this workflow
issues:opened)bot-research,bot-propose-fix,bot-advisor,bot-address-review,bot-write-docs(issue_commentslash commands)bot-review-a,bot-tdd-gate(pull_request)Recovery for the 5 missed issues
Once this merges, re-trigger each via the new dispatch input:
Test plan
wheels consolecommands fail withmakeHttpPostinvalid argument error #2582 (or any one of the missed issues) and verify a triage comment appearsissues:openedtriage run🤖 Generated with Claude Code