@@ -12,8 +12,9 @@ name: Triage issue with Claude
1212#
1313# Security: the issue body/comments are untrusted input. Triage is split across
1414# two jobs so the writable token is never present while the model runs:
15- # * `triage` runs Claude with `contents: read` only — the GITHUB_TOKEN in its
16- # environment cannot write to issues. Claude also runs fully offline (no
15+ # * `triage` runs Claude with read-only permissions (contents + issues read,
16+ # to download the issue) — the GITHUB_TOKEN in its environment can read but
17+ # cannot write to issues. Claude also runs fully offline (no
1718# WebFetch/WebSearch and no Bash), so it cannot follow links or exfiltrate
1819# anything. It only produces a local report file.
1920# * `comment` is a separate, deterministic job that holds `issues: write` and
3233 description : " Issue number to triage"
3334 required : true
3435 type : string
36+ # TEST ONLY: run the split workflow on push to the test branch so it can be
37+ # exercised without opening an issue. Falls back to issue 2827 (see prep step).
38+ # Remove this trigger before merging.
39+ push :
40+ branches :
41+ - 06/15/26/triage_issue_wf
3542
3643# Least privilege by default; each job narrows or widens this as needed.
3744permissions :
@@ -45,15 +52,20 @@ jobs:
4552 if : >-
4653 github.event_name == 'workflow_dispatch' ||
4754 github.event_name == 'issues' ||
55+ github.event_name == 'push' ||
4856 (github.event_name == 'issue_comment' &&
4957 github.event.issue.pull_request == null &&
5058 startsWith(github.event.comment.body, '/triage') &&
5159 contains(fromJSON('["OWNER","MEMBER","COLLABORATOR"]'), github.event.comment.author_association))
5260 runs-on : ubuntu-latest
5361 timeout-minutes : 15
54- # Read-only: the token handed to Claude below cannot write to issues.
62+ # Read-only. `issues: read` is needed because the prep step downloads the
63+ # issue with `gh issue view`; with explicit permissions, unspecified scopes
64+ # default to none. The token handed to Claude can read the issue but cannot
65+ # write to it — the privileged `comment` job holds `issues: write`.
5566 permissions :
5667 contents : read
68+ issues : read
5769 concurrency :
5870 group : claude-issue-triage-${{ github.repository }}-${{ github.event.inputs.issue_number || github.event.issue.number }}
5971 cancel-in-progress : true
8193 # issue that triggered the event (opened issue or commented issue).
8294 ISSUE="${INPUT_ISSUE:-}"
8395 [ -z "$ISSUE" ] && ISSUE="${EVENT_ISSUE:-}"
96+ # TEST ONLY: the push trigger has no issue context, so fall back to a
97+ # known issue to exercise the split workflow. Remove with the push
98+ # trigger before merging.
99+ [ -z "$ISSUE" ] && ISSUE="2827"
84100 if [ -z "$ISSUE" ]; then
85101 echo "::error::no issue number — pass issue_number or trigger on issues/issue_comment"
86102 exit 1
@@ -122,7 +138,7 @@ jobs:
122138 # commands, or exfiltrate anything. It cannot edit repo files or post
123139 # comments — the separate `comment` job does that deterministically.
124140 claude_args : |
125- --allowedTools "Read,Glob,Grep,Write"
141+ --allowedTools "Read,Glob,Grep,Write,Task "
126142 --disallowedTools "Edit,MultiEdit,NotebookEdit,WebFetch,WebSearch,Bash"
127143 --max-turns 40
128144 prompt : |
0 commit comments