Uh oh!
There was an error while loading. Please reload this page.
fix(closure-router): a manual issue close must not invent a deploy state - #67
Merged
Merged
Conversation
An issue closed by hand as completed was routed to Prod. The closing-PR lookup returns an empty base when no PR closed the issue, and the case statement's `*)` arm mapped that empty string to "Prod". This was an explicit default, not an accident of fallthrough, and it also swallowed the case where the GraphQL lookup simply failed. Observed live: backend#1171-#1176 were ticked off by hand on 2026-07-25 and all six were moved to Prod, despite their code having been merged only to develop. The run logs show "Routing decision: status=Prod" while closedByPullRequestsReferences returned an empty list for each. This matters beyond tidiness. fr-gate.yml is a required status check that lets a promotion PR merge only when every contained item sits in Ready for staging or Ready for prod. An item falsely parked in Prod satisfies that gate with nobody having reviewed it, so this bug can let unreviewed work through the very gate designed to stop it. Fix: separate "no closing PR" from "closing PR with an unrecognised base". With no closing PR the step now emits an empty status, and the update step (already guarded on a non-empty status) skips entirely, leaving the card in whatever column it was already in. Closing an issue by hand tells us nothing about where the code is deployed, so the honest answer is to change nothing. not_planned and a missing state_reason still route to Cancelled, and every PR-merge route is unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
LukasWodka
commented
Jul 25, 2026
ContributorAuthor
bugbot run |
LukasWodka
commented
Jul 25, 2026
ContributorAuthor
👋 Heads-up — Code review queue is at 35 / 30 Above the WIP limit. The team convention is to review existing PRs before opening new work. Open PRs currently in Code review (oldest first):
Pull from review before opening new work. (This is a nudge from the kanban WIP check, not a block.) |
There was a problem hiding this comment.
✅ Bugbot reviewed your changes and found no new issues!
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit cf86578. Configure here.
This was referenced Jul 25, 2026
Merged
Uh oh!
There was an error while loading. Please reload this page.
LukasWodka added a commit
that referenced
this pull request
Jul 26, 2026
* fix(fr-gate): gate the diff, not just commit subjects Item discovery greps commit SUBJECTS for (#N). Any commit whose subject has no marker — a hand-written commit, a rebase-merged commit, or the commits folded inside a merge — was invisible to the gate. design-system#70 shipped 12 such commits to main under a single blessed (#67): the gate saw one item, passed, and the other 12 were never evaluated. Adds an attribution pass over every non-merge commit in BASE..HEAD. A commit is attributable if its own subject carries a (#N), OR it is reachable from the 2nd parent of a merge in range (i.e. it entered via a PR merge). Whatever is left is code that reached the range without a reviewed PR — a direct push, or a release branch edited after its last PR — and it now fails the gate closed. Squash- and rebase-merges to develop each produce a single (#N)-bearing commit, so the normal flow attributes cleanly; only genuinely unreviewed commits flag. Verified against a git fixture: a subject-ref commit, two commits vouched for by their PR merge, and one direct-push commit — only the direct push is flagged. actionlint passes. Refs RFC-BACKEND-0008 D27-L1 (tracebloc/backend#1265) * fix(fr-gate): vouch a commit only if a merge introduced it (M^1..M^2) Bugbot on #72: the merge-vouching path treated any ancestor of a merge's 2nd parent as attributable. A direct push to the base branch becomes an ancestor of ^2 once a feature branch merges the base in and is then PR-merged, so it was silently vouched despite never being in a PR. Tighten to: reachable from ^2 AND NOT from ^1 — i.e. among the commits the merge actually added. Verified against a fixture reproducing exactly that pattern: the old check flagged nothing, the new check flags the direct push, and the normal feature-merge fixture still flags only its own direct push (no false positives introduced).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closing an issue by hand as completed moved its kanban card to
Prod. This fixes that: with no closing PR, the router now leavesStatusalone.The mechanism (not a guess — traced end to end)
In the
issues+state_reason=completedbranch, the router asks GraphQL for the base branch of the PR that closed the issue:That empty string then fell into the
casestatement's last arm:So it was an explicit default, not a fallthrough accident and not a mis-detected closing PR. The same arm also swallowed a failed lookup — the
ghcall ends in|| CLOSING_PR_BASE="", so a transient GraphQL error producedProdtoo.Evidence
backend#1171–#1176were closed by hand as completed on 2026-07-25 09:21 UTC. All six were moved toProd, though their code had been merged only todevelopand never promoted. They had to be corrected manually afterwards.Confirmed against the live API and the run logs:
closedByPullRequestsReferences(first: 5)→{"nodes":[]}for each issueRouting decision: status=Prod→→ #1171 → Status=ProdWhy this is more than untidy
fr-gate.ymlis a required status check. It lets a promotion PR merge only when every contained item is inReady for staging(→ staging) orReady for prod(→ main/master). An item sitting falsely inProdsatisfies that gate without anyone having reviewed it. A hand-closed issue could therefore let unreviewed work through the exact gate built to stop it.The fix
Split the two cases that were collapsed into one arm:
ProdProdProdFR on devWith no closing PR the step emits an empty
status, and the update step — already guarded byif: steps.target.outputs.status != ''— skips entirely. The card keeps whatever column it was in, which is the honest answer: ticking an issue off by hand tells you nothing about where its code is deployed.advance-deploy-env.ymlstill moves the card when the code actually ships, andkanban-reconcile.ymlstill catches drift.No new column was invented.
One judgement call worth flagging
The third row above is a small behaviour change beyond the reported bug. An issue closed by a PR merged to an unrecognised base used to become
Prod, while thepull_requestbranch maps that same base toFR on dev. The documented contract is "an issue mirrors its closing PR", so the two disagreeing was itself a defect — andProdis the same invent-a-deploy-state failure, reachable through a different input. It now mirrors the PR route. Happy to revert this one hunk if you'd rather keep the change strictly to the reported case.Deliberately not changed (flagging, not fixing)
closedByPullRequestsReferencesdefaults toincludeClosedPrs: false, which still returns open PRs that say "Closes #N". So an issue closed by hand while an open promotion PR targetingmainreferences it will mirror toProd— same bug class, different input. Filtering the query to merged PRs only would close it. I left it alone because it changes the documented PR-mirroring path rather than the manual-close path, and this workflow is consumed by ~17 repos. Worth a follow-up.What was deliberately left alone
main/master→Prod,staging,develop, other base) — byte-identical.Cancelled.not_plannedand issue with nostate_reason→Cancelled.Update Status on the kanbanstep — untouched.SC2016findings in that second step: they belong to ci(actionlint): lint this repo's own workflows + clear the 29 pre-existing findings #66, which exists to clear them. Not duplicated here to keep the diffs disjoint.The
run:block was refactored to assign aSTATUSvariable and write$GITHUB_OUTPUTonce at the end. Same values, but "no change" becomes expressible, and it removes the oldgrep status= "$GITHUB_OUTPUT" | tail -1 | cut -d= -f2log line, which would have broken as soon as a branch wrote no status at all.Verification
Route matrix, old script vs new, run under a stubbed
gh— 13 of 16 identical, and the 3 that changed are precisely the bug class:actionlint (
actionlint -no-color -oneline -shellcheck shellcheck, v1.7.12 + shellcheck 0.11.0, same invocation as #66's job) on this file:SC2016)Zero findings are attributable to this diff. The one
SC2016inside the step I edited is now suppressed at the line with a reason, using the same wording as #66 so the two resolve trivially. YAML parses; both script versions passbash -n.Notes for the reviewer
# shellcheck disable=SC2016on the exact line this rewrites. Resolution is to keep this PR's block, which already carries the directive.Item closedworkflow is disabled on project chore: add auto-add to engineer kanban workflow #2 (verified via the API — workflow #7,enabled: false), so nothing else claims the card on close. The in-code comment records this, because if it is ever re-enabled this branch needs revisiting. That also makes the stale warning in the existingenv:comment worth a second look someday.Also hardens
${{ github.event.issue.number }}out of therun:body intoenv:, matching the convention the rest of the file already follows.🤖 Generated with Claude Code
Note
Medium Risk
Changes promotion-gate-related kanban routing across many repos; wrong behavior could block or mis-route releases, though the fix corrects a known false-Prod path.
Overview
Kanban closure routing no longer treats a manually completed issue (or a failed closing-PR GraphQL lookup) as shipped:
Statusstays unchanged because the router leavesstatusempty and the existing Update Status step skips when output is blank.Previously, an empty
CLOSING_PR_BASEhit thecasedefault and forcedProd, which could satisfyfr-gate.ymlwithout real functional review. The header comment and an in-script note document that deliberate “no default” behavior and the dependency on the disabled Item closed project workflow.The Determine target Status script is refactored to set a single
STATUSvariable and writeGITHUB_OUTPUTonce;ISSUE_NUMBERmoves intoenvfor theghcall. Issues closed by a PR with an unrecognised base now map toFR on dev(matching thepull_requestmerge path) instead ofProd.Reviewed by Cursor Bugbot for commit cf86578. Bugbot is set up for automated code reviews on this repo. Configure here.