Summary
Triggered after Gate B triage routes a finding to deferral: out-of-scope P2s and all P3s. Reads the single finding by id from docs/findings.json — does not iterate the array. Refuses P1 findings (which are merge blockers, not deferral candidates). Runs a dedup check against open deferred-from-loop issues before creating a new one. Files the issue with an actionable title, distilled body, and correct labels. Appends to docs/issues-filed.json whether the URL came from a new or pre-existing issue. Operates on exactly one finding per invocation; parallelism comes from running multiple instances against independent findings.
Procedure
Step 1 — Read and validate the finding
Read docs/findings.json. Locate the entry whose id matches the provided finding_id. Do not iterate every finding — read the file once and extract the single matching entry.
If no entry with the given finding_id exists, stop immediately and output:
Cannot defer: finding '<finding_id>' not found in docs/findings.json.
Verify the id and re-run, or check that review-fanout has been run for this workflow.
If the found entry has priority: P1, stop immediately and output:
Refused: finding '<finding_id>' is priority P1.
P1 findings are merge blockers and must be resolved, not deferred.
Route this finding to resolve-finding instead.
Do not modify any files. Do not proceed.
Record these fields from the finding for use in subsequent steps:
idpriorityin_scopereviewertitledescriptionlocation(optional)reproducer(optional)suggested_fix(optional)
Step 2 — Dedup check
Before creating an issue, search for an existing open GitHub issue that already covers this finding.
gh issue list --label "deferred-from-loop" --search "<short keyword from finding title>" --state open --json number,title,url,body
Scan the returned issues. A match is a finding whose title and description address the same root cause in the same location — not just surface keyword overlap. Use judgment: if the candidate issue clearly covers the same problem, it is a match. If uncertain, do not treat it as a match — create a new issue.
If a clearly matching open issue exists:
Append to docs/issues-filed.json:
{ "finding_id": "<finding_id>", "url": "<existing-issue-url>", "filed_at": "<ISO-8601 datetime>", "dedup": true }
Output:
Deferred (dedup): <finding_id>
Matched existing issue: <existing-issue-url>
No new issue created.
Recorded in docs/issues-filed.json.
Stop. Do not create a new issue.
If no matching issue exists: continue to Step 3.
Step 3 — Create the GitHub issue
Create an issue with gh issue create. Do not leave blank labels or omit required fields.
Title: Rewrite the finding title to be actionable and reader-friendly. Drop reviewer jargon and internal ids. Format: imperative verb + concrete subject. Example: finding: "Missing index on notification.user_id" → issue: "Add database index on notifications.user_id for read query performance".
Body: Include all five of the following sections in this order:
- Summary (one paragraph): what the problem is, where it lives, and why it matters. Distill the finding description; do not copy it verbatim.
- Location: the
location.path(and line range if present). If no location, write "Location: not location-bound." - Context: reviewer that found it (
reviewerfield) and the workflow it came from (workflow_idinput). Example:Found by: security-sentinel | Workflow: add-email-notifications-mention. - Deferral reason: one of "Out of scope for workflow
<workflow_id>" (for out-of-scope P2s) or "P3 — lower-priority improvement" (for P3s). Do not embellish. - Reproducer (if available): paste or summarize
finding.reproducer. If absent, omit the section entirely — do not write "Reproducer: N/A".
Labels: Apply all of the following:
- Map the reviewer's
categoryto a matching label (security,performance,architecture,quality, or the closest match). Rungh label listif uncertain whether the label exists; do not invent labels. deferred-from-loop— required; this is howharvestfinds deferred items later.priority:P2orpriority:P3— matching the finding's priority.
Assignees: Assign to the repo's owner or team lead per project conventions. For this repo, assign to jdforsythe.
Capture the URL of the created issue.
Step 4 — Record the URL
Append to docs/issues-filed.json. The file is a JSON array. Read it in full, append one new entry, and write it back:
{ "finding_id": "<finding_id>", "url": "<gh-issue-url>", "filed_at": "<ISO-8601 datetime>", "dedup": false, "labels_applied": ["<label1>", "<label2>"] }
If docs/issues-filed.json does not exist, create it as an empty array [] before appending.
Step 5 — Confirm to the user
Output a confirmation:
Deferred: <finding_id> (<priority>)
Title: <issue title>
Issue: <gh-issue-url>
Labels: <comma-separated list>
Recorded in docs/issues-filed.json.
Constraints
- One finding per invocation. Never iterate
docs/findings.jsonto defer multiple findings. Each invocation targets exactly onefinding_id. - Refuse P1 findings. If
priority: P1, stop at Step 1 without creating any issue. - Dedup before creating. Always run the dedup check in Step 2. Creating a duplicate issue is the primary failure mode.
- Actionable title. The rewritten title must use an imperative verb and name a concrete subject. Do not copy the finding's raw title.
- Distilled body. Issue bodies are for triage — write to be scanned in seconds. Do not paraphrase the finding verbatim or pad with reviewer metadata.
- Required label:
deferred-from-loop. This label is howharvestdetects recurring deferral categories. It must be present on every issued filed by this skill. - Never modify
docs/findings.json. The findings file is the output ofreview-fanoutand is read-only from the perspective of this skill. docs/issues-filed.jsonis a JSON array. Read-append-write atomically; never truncate or overwrite with only the new entry.- Never file P1 findings as issues. P1 findings that reach this skill indicate a filter error upstream. Refuse and report; do not silently downgrade to P2.