skills/flow-triage-issue/SKILL.md
Triage a single open GitHub issue from a PM lens. Applies a 'Triage In-Progress' label during triage; reads code, checks for already-shipped work, returns a verdict in {close, decompose} with confidence and a flip-condition. Renders and stops — no other side effects.
npx skillsauth add benkruger/flow flow-triage-issueInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Run a structured per-issue triage from a PM-with-engineering-literacy
lens. The skill fetches the issue, reads referenced code (or
grep-locates behavior when unreferenced), checks for already-shipped
work via gh pr list --search and git log --all --grep, and answers
10 triage questions plus a verdict card. The verdict is rendered
inline and the skill STOPS — the PM acts manually.
The triage Process is inlined directly in this SKILL.md as Steps 3-7.
You are a senior PM with engineering literacy: read code before
judging an issue's claims (per .claude/rules/assess-issues.md), and
answer in user/business terms. The PM consuming your verdict has not
seen the issue or the code — your output must stand alone.
/flow:flow-triage-issue #1234
The argument is #N — a literal # followed by a positive integer
issue number in the current repository (whichever repo gh
resolves to). v1 is open issues only — closed issues are refused
with an out-of-scope envelope.
This skill mutates two surfaces of GitHub state. Per triage, it applies a "Triage In-Progress" label assignment to the issue at the start of triage and removes it before the COMPLETE banner. One-time per repository, Step 2 also creates the "Triage In-Progress" label in the repository's label registry on the first triage in a fresh repo — the registry write is idempotent in intent (the steady-state path checks for existence first and skips the create). The skill never closes, comments on, or applies any other label.
The per-issue label assignment is a passive observability signal —
a second PM running /flow:flow-triage-issue on an issue already
labeled sees the in-progress signal in the GitHub UI before
invoking; it is NOT a mutex, so concurrent triages of the same
issue are tolerated and whichever invocation removes the label
last wins. If the skill crashes between the add and remove, the
label persists; manual removal from the GitHub UI is the recovery
path. The investigation gh issue view, gh pr list, and
git log calls remain read-only — beyond the label add/remove,
this skill never mutates GitHub or git state.
At the very start, output the following banner in your response (not via Bash) inside a fenced code block:
```text
──────────────────────────────────────────────────
FLOW v2.6.1 — flow:flow-triage-issue — STARTING
──────────────────────────────────────────────────
```
Read the argument string. Strip surrounding whitespace.
The argument MUST match the regex ^#[1-9][0-9]*$ exactly — a
literal # followed by a positive decimal integer with no leading
zero, no sign, no decimal point, no scientific notation, no
whitespace, no quotes, no flags. The strict # prefix matches the
sibling /flow:flow-start and /flow:flow-plan argument
formats so issue references are unambiguous across the FLOW skill
family. The strict shape rejects argument-injection vectors like
#42 --repo other/repo, regex-metacharacter values like #1[23],
floats like #1.5, and zero/negative values that the GitHub API
treats as flags.
# if the user
omitted it), then re-validate against the regex above.```text
Error: /flow:flow-triage-issue requires `#N` where N is a positive integer.
Got: <argument>
Usage: /flow:flow-triage-issue #<issue-number>
```
# and keep the
numeric value as <issue_number> for the remaining steps.Apply a "Triage In-Progress" label to the issue so concurrent sessions can see at a glance that the issue is being triaged. The label is passive — it does not block other invocations — but it makes parallel work visible in the GitHub UI.
First, check whether the label already exists in the repository.
gh issue edit --add-label rejects unknown labels, so a fresh
repo needs the label created before the per-issue assignment can
land:
gh label list --search "Triage In-Progress" --json name
Parse the JSON output. The result is a JSON array — empty []
means the label is absent and must be created; any non-empty array
containing an entry whose name field equals "Triage In-Progress"
means the label already exists and the create call must be
skipped.
If the list-search is empty (no existing label), create it:
gh label create "Triage In-Progress" --description "PM is currently triaging this issue" --color FFA500
If gh label create fails for any reason in this branch (network,
auth, permission), halt the skill and surface the error to the
user — do NOT proceed to add-label, because the assignment will
also fail.
If the list-search returned a non-empty array containing "Triage In-Progress", skip the create call entirely and proceed to the assignment.
Add the label to the issue:
gh issue edit <issue_number> --add-label "Triage In-Progress"
<issue_number> is the validated numeric value from Step 1.
Steps 3 through 7 perform read-only investigation and verdict
construction. If any unexpected error fires during these steps —
an unhandled gh failure, a Read tool error, a network blip, a
session interrupt — control MUST still reach Step 8's label
removal before the COMPLETE banner. The "Triage In-Progress"
label is shared GitHub state; an orphaned label requires manual
removal from the GitHub UI to recover.
Documented exit paths that route through Step 8:
state == CLOSED in Step 3 → render out-of-scope envelope in
Step 7 → continue to Step 8 (label remove)Undocumented error paths: when any tool call between Step 3 and
Step 7 surfaces an error the skill does not name, recover by
running gh issue edit <issue_number> --remove-label "Triage In-Progress" BEFORE reporting the error to the user. The
label-remove must always be the final mutation regardless of
which step the error fired in.
Run:
gh issue view <issue_number> --json title,body,labels,state,createdAt,updatedAt,comments,author
If state is CLOSED, proceed directly to Step 7 and render the
out-of-scope envelope with Reason: closed. Then continue to
Step 8 for label removal.
If the fetch fails (404, auth error, network error), proceed
directly to Step 7 and render the out-of-scope envelope with
Reason: fetch_failed or Reason: not_found as appropriate.
Then continue to Step 8 for label removal.
Otherwise capture the issue body, labels, state, comments, and author for use in Steps 4-7.
Scan the issue body for backtick-quoted file paths and path:line
references. Read every file referenced in the body via the Read
tool.
If the body names no files, search the codebase for the behavior
described, then read the implementation. Per
.claude/rules/assess-issues.md "When the Issue Names No Files",
the grep is to locate code, not to confirm the issue. After
locating the code, compare current behavior against the issue's
claims independently.
Read code BEFORE judging the issue's claims, never the other way around. A grep that confirms a phrase from the issue body is confirmation bias — it locates code but does not verify the claim. Verification is a separate Read of the current implementation.
Per .claude/rules/assess-issues.md "Check for Already-Shipped
Work", run:
gh pr list --search "<issue_number>" --state merged --json number,title,mergedAt,url
gh pr list --search "<issue_number>" --state open --json number,title,url
git log --all --grep "#<issue_number>" --oneline
For every merged PR that referenced the issue, read the cited code to verify what shipped. A merged PR that referenced the issue without closing it is strong evidence the work shipped — verify by reading the cited code rather than trusting the PR title alone. If the work shipped but the issue remained open, the issue may describe a follow-up gap or may be ready to close.
Use plain English. Cite file:line for every code claim. A
claim without a citation is speculation.
Use Premise → Trace → Conclude reasoning per
.claude/rules/semi-formal-reasoning.md:
Findings with incomplete traces must be discarded, not reported with caveats. If you cannot complete the trace (network failure, file inaccessible, ambiguous semantics), say so explicitly in the answer to question 2 ("Still real?") and lower the confidence level accordingly.
The issue body describes the symptom from the filer's perspective.
Your job is not to accept that perspective — it is to test it
against the code. When the issue frames a hook, gate, or guard
as broken because it blocked an action the filer wanted to take,
treat the block as presumptively intentional per
.claude/rules/filing-issues.md "Mechanical Blocks Are
Presumptively Intentional".
Before producing the verdict, read three artifacts in order:
src/hooks/<name>.rs — names
what failure mode the block prevents..claude/rules/ cited by the module doc —
describes the design intent in prose.tests/hooks/<name>.rs — shows the canonical
block cases and authorized inputs.State your framing challenge explicitly in section 3 (Framing). The block is a real defect ONLY when one of these holds:
Framings that describe the block doing its job — "the model
couldn't proceed autonomously", "I wanted it to ask the user
instead", "the recovery requires user intervention", "the flow
stalled until the user typed a continue token" — are NOT valid
grounds for a decompose disposition. The presumptive verdict
in those cases is close with a flip-condition naming the
specific input or contradiction that would make the block a
real defect.
Use the exact heading shapes below — the 10 question markers are locked in by contract tests. Answer in order, one heading per question.
### 1. Real? [answer + evidence]
### 2. Still real? [answer + current code state]
### 3. Framing [actual problem or symptom]
### 4. What (plain English)
### 5. Why care (plain English)
### 6. Who's affected + severity
### 7. Urgency
### 8. How would this be fixed
### 9. What success looks like
### 10. Risk of the fix
Build the 5-field verdict card. Use the exact heading shapes below — the 5 verdict-card fields are locked in by contract tests.
### Verdict
- **Disposition:** {close | decompose}
- **Summary:** [one paragraph]
- **Evidence:** [bulleted file:line refs]
- **Confidence:** {low | medium | high} — [one-line rationale]
- **This flips if:** [what would change the disposition]
The closed set is {close, decompose}. Pick exactly one:
/flow:flow-start. The PM should
invoke /flow:flow-plan #N against the vanilla problem
statement to decompose it into a linked decomposed issue, then
close the original (or leave it open as the durable problem
statement and start the flow against the new decomposed
issue).When the issue cannot be triaged because it is closed or the fetch failed (Step 3), replace the 10-question lens with a single section:
### Out of scope
- **Reason:** {closed | fetch_failed | not_found}
- **Detail:** [one-line explanation]
- **Next step for the PM:** [what the PM can type or do next]
Do NOT produce a verdict card in this case.
The verdict has been rendered (or the out-of-scope envelope was
displayed). Remove the "Triage In-Progress" label so the issue no
longer signals active triage. The Step 9 HARD-GATE that follows
forbids any further gh issue edit calls — the label cleanup must
land in this step before the gate fires.
gh issue edit <issue_number> --remove-label "Triage In-Progress"
This HARD-GATE is mechanical. After Step 8 completes you must NOT:
gh issue close, gh issue edit,
gh issue comment, or any other GitHub-state-mutating
subcommand (Step 8's gh issue edit ... --remove-label was the
final permitted invocation; no further gh issue edit calls are
permitted from this point)git command that writes (commit, push, tag, etc.)The PM reads the verdict and decides what to do. Print a brief hint describing the next manual step based on the disposition, inside a fenced code block. Describe the action in prose — do NOT include slash-command literals that the model could be tempted to invoke. The PM types the next command themselves.
Then output the COMPLETE banner and stop. Do not run any other tool or invoke any other skill. </HARD-GATE>
Output the following banner in your response (not via Bash) inside a fenced code block:
```text
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✓ FLOW v2.6.1 — flow:flow-triage-issue — COMPLETE
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
```
.claude/rules/assess-issues.md).file:line for every code claim. A claim without a
citation is speculation.gh issue close.gh issue comment./flow:flow-explore, /flow:flow-plan,
/flow:flow-start, or any other skill based on the verdict. The
PM acts manually.{close, decompose}. The closed
set is locked by contract test; never introduce additional
values.disallowedTools: Edit, Write sandbox; this rule is the
instruction-level recovery of that constraint.)gh subcommands invoked
during Steps 3-5 (gh issue view, gh pr list) are read-only;
never run any gh subcommand outside that read-only set plus
the gh issue edit --add-label/--remove-label calls in
Steps 2 and 8.data-ai
Clear the autonomous-flow halt set when the user spoke mid-flow. Invokes `bin/flow clear-halt` so the next assistant turn resumes execution. User-only: the model cannot invoke this skill.
data-ai
Open a problem-statement conversation. Stays in discussion mode with PM as default voice; on user signal, files a vanilla What/Why/Acceptance Criteria issue against the current repo. Usage: /flow:flow-explore <topic>
tools
Display the FLOW skill catalog grouped by user role. Maintainer and Private buckets render only when invoked inside the FLOW plugin repo.
documentation
Phase 3: Review — six tenants assessed by four cognitively isolated agents (reviewer, pre-mortem, adversarial, documentation) launched in parallel. Parent session gathers context, triages findings, and fixes.