Summary
create-gh-code-scanning-issues.yml can create and update alert-tracking issues, but it has no path that closes one. When an alert is fixed or dismissed, its tracking issue stays open indefinitely and must be closed by hand.
Eight automated issues accumulated this way and were closed manually on 2026-08-23: #2725, #2652, #2462, #2460, #1499, #1500, #1502, #2461.
Why the workflow cannot close anything today
Get-CodeScanningAlerts.ps1 fetches with state=open, so alerts.json contains only live alerts. Every action in the workflow - create, edit, comment - sits inside a single loop over that file:
done < <(jq -c '.[]' alerts.json)
A per-item loop can only act on rules that are present. A fixed or dismissed alert simply disappears from the input, so the workflow never observes its absence. Closing requires a set difference over what is missing, which this shape cannot express. The gap is structural, not a missing branch in the loop body.
Proposed fix
Add a reconciliation step after the loop:
- Collect the rule IDs present in
alerts.json.
- List open issues whose body contains the
automation:security-scan: marker.
- Parse each issue's rule ID from its marker comment.
- Close issues whose rule ID is absent from the live set, with a comment naming the run that observed the alert as no longer open.
The guard is the hard part
An unguarded reconciliation pass is a mass mutation with a fail-open failure mode: any run where alerts.json is empty, truncated, or malformed would close every open alert issue at once.
Emptiness cannot be the guard - zero open alerts is a legitimate and reachable state. The guard must be artifact validity: abort before closing anything unless the file parses as a JSON array.
Note that needs: [gh-code-scanning] does not cover this. It blocks on job failure, but not on a job that succeeds while producing a bad artifact. The serialization defect filed alongside this issue is exactly that case: the step succeeded and wrote syntactically valid JSON of the wrong shape. The artifact-upload step also carries if: always(), so artifact existence is not evidence that the scan succeeded.
Two related defects in the same step
Deduplication only sees open issues. The lookup uses --state open. If an issue is closed while its alert is still open, the next run files a duplicate instead of reopening. Once a close path exists, reopen-if-closed becomes the correct behavior; otherwise the two mechanisms will fight.
The update branch overwrites human edits. gh issue edit --title --body --add-label "automated,needs-triage" rewrites the entire body weekly and re-adds needs-triage to issues someone has already triaged. The weekly delta already goes in the comment the step posts; the body rewrite should be dropped or confined to a generated region.
Sequencing
The serialization fix should land first and separately. It is a one-word repair to a currently-red workflow and should not sit behind review of a mass-mutation feature.
Acceptance criteria
Related
Summary
create-gh-code-scanning-issues.ymlcan create and update alert-tracking issues, but it has no path that closes one. When an alert is fixed or dismissed, its tracking issue stays open indefinitely and must be closed by hand.Eight automated issues accumulated this way and were closed manually on 2026-08-23: #2725, #2652, #2462, #2460, #1499, #1500, #1502, #2461.
Why the workflow cannot close anything today
Get-CodeScanningAlerts.ps1fetches withstate=open, soalerts.jsoncontains only live alerts. Every action in the workflow - create, edit, comment - sits inside a single loop over that file:A per-item loop can only act on rules that are present. A fixed or dismissed alert simply disappears from the input, so the workflow never observes its absence. Closing requires a set difference over what is missing, which this shape cannot express. The gap is structural, not a missing branch in the loop body.
Proposed fix
Add a reconciliation step after the loop:
alerts.json.automation:security-scan:marker.The guard is the hard part
An unguarded reconciliation pass is a mass mutation with a fail-open failure mode: any run where
alerts.jsonis empty, truncated, or malformed would close every open alert issue at once.Emptiness cannot be the guard - zero open alerts is a legitimate and reachable state. The guard must be artifact validity: abort before closing anything unless the file parses as a JSON array.
Note that
needs: [gh-code-scanning]does not cover this. It blocks on job failure, but not on a job that succeeds while producing a bad artifact. The serialization defect filed alongside this issue is exactly that case: the step succeeded and wrote syntactically valid JSON of the wrong shape. The artifact-upload step also carriesif: always(), so artifact existence is not evidence that the scan succeeded.Two related defects in the same step
Deduplication only sees open issues. The lookup uses
--state open. If an issue is closed while its alert is still open, the next run files a duplicate instead of reopening. Once a close path exists, reopen-if-closed becomes the correct behavior; otherwise the two mechanisms will fight.The update branch overwrites human edits.
gh issue edit --title --body --add-label "automated,needs-triage"rewrites the entire body weekly and re-addsneeds-triageto issues someone has already triaged. The weekly delta already goes in the comment the step posts; the body rewrite should be dropped or confined to a generated region.Sequencing
The serialization fix should land first and separately. It is a one-word repair to a currently-red workflow and should not sit behind review of a mass-mutation feature.
Acceptance criteria
alerts.jsonis missing or does not parse as a JSON arrayneeds-triageunconditionallyRelated