Skip to content

fix(kanban): abort when the Status option cannot be resolved, instead of warning - #246

Merged
LukasWodka merged 1 commit into
developfrom
fix/1943-status-resolve-fail-closed
Aug 13, 2026
Merged

fix(kanban): abort when the Status option cannot be resolved, instead of warning#246
LukasWodka merged 1 commit into
developfrom
fix/1943-status-resolve-fail-closed

Conversation

@LukasWodka

@LukasWodka LukasWodka commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Bugbot High on the staging promotion PR #243.

The bug

An unresolvable Status option logged ::warning::, set SKIP_STATUS=1, and let the run finish green. The board silently stops advancing while every push reports success — the failure is indistinguishable from the success, and the only signal is a warning in a log nobody opens.

This was the one asymmetry in the family:

path on unresolved option
advance-deploy-env — Deploy environment (12 lines above) exit 1
kanban-closure-router:230 — Status exit 1
advance-deploy-env — Status warning, green run

And Status is the half that drives the pipeline view.

What I did not change, having checked it

The finding also suggests the writers lost their rename-window fallback while the resolvers kept it. They did — but it is not live. I read the live project: the board’s Status options are Staging (agent review) and FR on staging, and the writers emit FR on staging, so every lookup resolves today.

opt_either("Staging (human review)", "FR on staging") in kanban-reconcile.yml is now vestigial — its own comment says it is the backend#1592 rename shim, "removed in step 3". Finishing that retirement is a five-file sweep and a separate change from this abort, so I have not folded it into a release-freeze fix.

Also

Corrected the comment above the block, which still described the removed behaviour ("degrades gracefully … rather than failing the whole workflow") and would have read as the contract to the next person.

actionlint clean; YAML parses.


Note

Low Risk
CI-only fail-closed change for kanban misconfiguration; no app runtime impact, though a bad Status mapping will now fail promotions that previously appeared successful.

Overview
advance-deploy-env no longer treats a missing or unresolvable GitHub Projects Status field/option as a soft skip. Misconfigurations (column rename, bad .kanban.yml, wrong project) now emit ::error:: and exit 1, matching Deploy environment resolution in the same step and kanban-closure-router.

Previously the workflow logged ::warning::, set SKIP_STATUS=1, and finished green while Status updates were skipped—so the kanban pipeline could stall with no obvious failure. SKIP_STATUS is always 0 after resolution (guards below stay unchanged); comments document the old “degrade gracefully” behavior as the bug.

Reviewed by Cursor Bugbot for commit a0c8deb. Bugbot is set up for automated code reviews on this repo. Configure here.

… of warning

Bugbot HIGH on the staging promotion PR #243.

An unresolvable Status option logged `::warning::`, set SKIP_STATUS=1 and let the
run finish GREEN. So the board silently stops advancing while every push reports
success -- the failure and the success are indistinguishable, and the only signal
is a warning in a log nobody opens.

This was the one asymmetry in the family. Twelve lines above, the Deploy
environment lookup aborts on exactly this condition; kanban-closure-router.yml
aborts on it too (":230 Could not resolve Status option - aborting; exit 1").
Only this path degraded, and Status is the half that drives the pipeline view.

WHAT I DID NOT CHANGE, having checked it: the finding also suggests the writers
lost their rename-window fallback while the resolvers kept it. They did, but that
is not live -- the board's Status options today are `Staging (agent review)` and
`FR on staging` (read from the live project), and the writers emit `FR on
staging`, so every lookup resolves. `opt_either('Staging (human review)', 'FR on
staging')` in kanban-reconcile.yml is now vestigial: its own comment says it is
the backend#1592 rename shim, "removed in step 3". Finishing that retirement is a
five-file sweep and a separate change from this abort; filed rather than folded
in during a release freeze.

Also corrected the comment above the block, which described the removed
behaviour ("degrades gracefully ... rather than failing the whole workflow") and
would have read as the contract to the next person.

actionlint clean; YAML parses.
@LukasWodka LukasWodka self-assigned this Aug 13, 2026
@LukasWodka
LukasWodka requested review from saadqbal and shujaatTracebloc and removed request for shujaatTracebloc August 13, 2026 18:26
@LukasWodka
LukasWodka merged commit 9bb10bd into develop Aug 13, 2026
11 checks passed
LukasWodka added a commit that referenced this pull request Aug 13, 2026
…it (#247)

* feat(kanban): assert the board has every Status column the writers emit

The board's column names and the workflows that write them are two systems that
must agree, and nothing checked that they did. #1592 carried the `FR on staging`
rename by hand across three PRs; in between, the writers emitted a column the
board did not have and only a fallback made it work.

That gap is exactly what .github#243 reported and what #245 closed by hand. This
makes it un-driftable instead of remembered.

The failure it prevents is a silent one. A written name that does not resolve
means the card is not moved -- and until #246 that was a `::warning::` on a green
run, so a frozen board and a working board looked identical.

SCOPE IS DELIBERATELY NARROW:

  * WRITERS are asserted. A name that is written must exist or the write cannot
    land. `advance-deploy-env.yml` and `kanban-closure-router.yml`, listed rather
    than globbed -- `STATUS=` is a common shell variable and other workflows use
    it for "ok"/"absent"/"unreadable", which are not column names.
  * RESOLVERS are NOT asserted. They ask for names on both sides of a rename on
    purpose: `opt_either("Staging (human review)", "FR on staging")` is the #1592
    shim and its first argument is SUPPOSED to be absent today. Enforcing there
    would make the tolerance impossible to express.
  * `STATUS_NAME="$OVERRIDE"` is skipped: a per-repo .kanban.yml value cannot be
    known here.

Runs on PRs touching the writers, and DAILY -- the board can be renamed in the UI
at any time with no PR to hang a check on, which is the failure this is really
for.

Fails closed throughout: an unreadable board, an unparseable response, zero
options returned, or a literal pattern that matches nothing are all errors. The
last one matters most -- a stale regex would make the check pass vacuously, which
is the failure mode of every guard it is modelled on.

Verified: passes on today's board (5 names); reproducing the pre-#245 state
(writer emitting "Staging (human review)") fails and names the exact file and
value; selftest 6/6 with the board stubbed, covering absent names, a stale name
among good ones, and a case near-miss.

* fix(kanban): gate the conformance check on the selftest job

Bugbot (Medium): the check job ran in parallel with selftest and had no
needs: selftest. If the checker failure paths regress while the board still
matches, "Selftest the checker" goes red but "Written Status names exist on the
board" can still report success -- a conformance-named green from a broken guard.
caller-drift + standards-sync gate their audit on selftest for the same reason.

Add needs: selftest so the conformance verdict is only trusted once the checker
is proven able to fail.

.github#247.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
LukasWodka added a commit that referenced this pull request Aug 13, 2026
#248)

* fix(kanban): the column check missed three writers, and could not tell

Two Bugbot findings on the staging promotion PR #243, both on code I added today.

MEDIUM — the conformance check from #247 was itself incomplete, which is the
exact defect it exists to prevent. `WRITERS` named two files and `LITERAL`
matched one idiom, so it never saw:

  set-pr-status.yml    `echo "status_name=In progress"`   (UNQUOTED value)
  fr-pass-comment.yml  `NEXT="Ready for prod"`

Three column names -- In progress, Code review, Ready for prod -- were invisible
to a guard built to make a rename impossible to miss. It reported a clean sweep
of a subset, which reads exactly like a clean sweep of everything.

Widening the hand-list alone would rot the same way, so the names are now derived
a SECOND, independent way and the two are compared: any board column name
ASSIGNED in a writer file that no idiom matched means the idiom list is stale,
and that is an error rather than a smaller answer. Assignment sites only, whole
values -- so comments, the rank `case` arms and prose are ignored, and `Ready`
does not match inside `Ready for prod`.

It earned that immediately: the cross-check caught my own first attempt at the
widened regex, which required a trailing quote and silently stopped matching
`STATUS_NAME="FR on staging" ;;`. Without it I would have shipped a check that
found fewer names than before while printing "all names exist".

Coverage went 5 names / 2 files -> 8 names / 4 files.

LOW — the abort message added in #246 said "The Deploy environment write may
already have happened". It cannot have: that check runs at :190 and the first
`update_field` is at :314, so on that path nothing was written at all. Corrected
to say so, since the point of the message is to tell an operator where they
stand.

Selftest 8/8 (adds: an assigned-but-unmatched name is reported; a comment
mentioning a column is not). caller-drift selftest 162/0. ruff + actionlint clean.

* fix(kanban): the PR trigger must cover every writer, and now cannot drift

Bugbot Medium on this PR. I broadened `WRITERS` to four workflows and left
`kanban-columns.yml`'s `paths:` filter listing the original two — so a PR
touching only `set-pr-status.yml` or `fr-pass-comment.yml` never ran this check
and a bad column write could merge until the next cron.

Same shape as the defect this PR is already fixing: the guard is present, reports
green, and is not watching the thing it names. Two files apart this time instead
of two variables.

The filter now lists all four. But listing them is what went wrong once already,
so the selftest DERIVES the requirement: every entry in WRITERS must appear in
the workflow's `paths:` block, read out of the YAML rather than restated. Adding
a fifth writer without wiring its trigger is now a red test, not a silent gap.

Verified by removing `set-pr-status.yml` from the filter: the assertion fails.
Selftest 10/10 with it restored. ruff + actionlint clean.

This is the third instance today of "fixed the code, left the trigger config" --
e2e#81 was the same bug in a tsc dry-run paths filter. Recorded in the message
because the pattern is worth more than the fix.

* style(selftest): split a combined import (ruff E401)

It was in the block I added two commits ago and I claimed ruff clean without
reading the exit code -- the `&&` swallowed it. Checked properly this time.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant