Skip to content

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

Description

@sebastientaggart

Symptom

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

  1. 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's parser recognizes Closes #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.

  1. .github/workflows/label-linked-issues.yml fires on the wrong trigger. Current trigger:
    on:
    pull_request:
    types: [ready_for_review, labeled]
  2. 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.
  3. 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.
  4. 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:

  1. /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.
  2. 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"
  3. Consider renaming the workflow file to label-merged-prs-for-qa.yml to match what it actually does.
  4. Add a close-on-qa-passed.yml workflow: trigger on issues: [labeled], guard with if: github.event.label.name == 'qa-passed', call gh issue close
    --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.
  5. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    qa-passedQA completed, passes

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions