You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
QA handoff flow leaves tickets orphaned after deploy: label-linked-issues workflow doesn't fire on merge, uses wrong label, and there's no close-on-qa-passed automation #80
After a PR merges and the fix ships in a release, the linked issue stays OPEN with a qa-passed label indefinitely. No automation ever closes it — a
human has to notice and close it by hand. If the human doesn't notice, the ticket orphans silently and the project tracker drifts out of sync with
what's actually deployed.
Hit this on a downstream project that inherits the Code Cannon scaffolding. The specific ticket was still open a full day after the PR that fixed it
had been released to production. Investigating why turned up four distinct gaps in the dev → QA → done flow that all need to line up for the handoff
to work, and currently none of them do.
Evidence
closingIssuesReferences is empty on the merged PR, even though its body contains Closes owner/repo#NNN. Verified via GraphQL:
query { repository(owner:"...", name:"...") {
pullRequest(number: NNN) { closingIssuesReferences(first: 10) { nodes { number } } }
} }
Returns {"closingIssuesReferences": {"nodes": []}}. GitHub'sparserrecognizesCloses #N reliably for same-repo references but is brittle on the
Closes owner/repo#N fully-qualified form. If agents/templates emit the qualified form, the closing-references edge stays empty and every automation
downstream of that edge (GitHub's own auto-close, the label-linked-issues workflow, anything else using the GraphQL edge) silently no-ops.
.github/workflows/label-linked-issues.yml fires on the wrong trigger. Current trigger:
on:
pull_request:
types: [ready_for_review, labeled]
Semantically that's "handoff to reviewers", not "handoff to QA". The workflow name ("Label linked issues Ready for QA") and its comments describe a
merge-time action, but the actual trigger fires before code review. Merging a PR never runs this workflow — which is why no labels get applied at the
actual dev→QA handoff moment.
The workflow applies a label that doesn't match the rest of the QA scheme. It sets ISSUE_LABEL: "Ready for Review" but the populated label set (from
/setup) is lowercase-dashed: ready-for-qa, qa-passed, qa-failed. The "Ready for Review" label also exists as a separate label with its own color and
description, so repos end up with two parallel schemes — one the workflow writes, one humans use — and neither talks to the other.
Nothing closes an issue when qa-passed is applied. The presumed flow is:
PR merges → linked issue gets ready-for-qa
QA verifies → label flipped to qa-passed
qa-passed is set → issue closes as completed
Steps 1 and 3 are both missing from the current scaffolding. Step 2 is the only part that works, and it's manual.
Root cause
The "prevent auto-close until QA verifies" safeguard has been half-implemented. The blocking half happens by accident (because closing keywords aren't
populating the references edge anyway, GitHub's native auto-close never fires). The unblocking half — closing the ticket once QA actually passes — was
never built. Combined with the trigger/label mismatches in label-linked-issues.yml, the whole handoff loop is broken end-to-end.
Suggested fixes
Four small changes, all independent and low-risk:
/submit-for-review (and any PR-body templating in agent prompts): emit Closes #N, not Closes owner/repo#N, for same-repo references. This is the
single-point-of-failure fix — once closingIssuesReferences populates, every downstream automation starts working. Detect cross-repo vs same-repo by
comparing the target repo to github.repository.
label-linked-issues.yml: change the trigger to merge, and use the canonical label.
on:
pull_request:
types: [closed]
jobs:
label-linked-issues:
if: github.event.pull_request.merged == true
...
env:
ISSUE_LABEL: "ready-for-qa"
Consider renaming the workflow file to label-merged-prs-for-qa.yml to match what it actually does.
Decide on a single label scheme. Either retire the uppercase "Ready for Review" label from /setup's label seed list, or retire ready-for-qa. Having
both invites exactly the kind of cross-wiring seen in finding Add docs/ folder and slim down README #3 above. If the repo already has both populated from historical runs, a /setup audit
step could flag the duplication.
Symptom
After a PR merges and the fix ships in a release, the linked issue stays
OPENwith aqa-passedlabel indefinitely. No automation ever closes it — ahuman has to notice and close it by hand. If the human doesn't notice, the ticket orphans silently and the project tracker drifts out of sync with
what's actually deployed.
Hit this on a downstream project that inherits the Code Cannon scaffolding. The specific ticket was still open a full day after the PR that fixed it
had been released to production. Investigating why turned up four distinct gaps in the
dev → QA → doneflow that all need to line up for the handoffto work, and currently none of them do.
Evidence
closingIssuesReferencesis empty on the merged PR, even though its body containsCloses owner/repo#NNN. Verified via GraphQL:Closes owner/repo#N fully-qualified form. If agents/templates emit the qualified form, the closing-references edge stays empty and every automation
downstream of that edge (GitHub's own auto-close, the label-linked-issues workflow, anything else using the GraphQL edge) silently no-ops.
on:
pull_request:
types: [ready_for_review, labeled]
merge-time action, but the actual trigger fires before code review. Merging a PR never runs this workflow — which is why no labels get applied at the
actual dev→QA handoff moment.
/setup) is lowercase-dashed: ready-for-qa, qa-passed, qa-failed. The "Ready for Review" label also exists as a separate label with its own color and
description, so repos end up with two parallel schemes — one the workflow writes, one humans use — and neither talks to the other.
Steps 1 and 3 are both missing from the current scaffolding. Step 2 is the only part that works, and it's manual.
Root cause
The "prevent auto-close until QA verifies" safeguard has been half-implemented. The blocking half happens by accident (because closing keywords aren't
populating the references edge anyway, GitHub's native auto-close never fires). The unblocking half — closing the ticket once QA actually passes — was
never built. Combined with the trigger/label mismatches in label-linked-issues.yml, the whole handoff loop is broken end-to-end.
Suggested fixes
Four small changes, all independent and low-risk:
single-point-of-failure fix — once closingIssuesReferences populates, every downstream automation starts working. Detect cross-repo vs same-repo by
comparing the target repo to github.repository.
on:
pull_request:
types: [closed]
jobs:
label-linked-issues:
if: github.event.pull_request.merged == true
...
env:ISSUE_LABEL: "ready-for-qa"
--reason completed. Maybe 15 lines total. This is the missing half of the Improve skill preview descriptions in Claude CLI to show what each skill does #11-style safeguard.
both invites exactly the kind of cross-wiring seen in finding Add docs/ folder and slim down README #3 above. If the repo already has both populated from historical runs, a /setup audit
step could flag the duplication.