Uh oh!
There was an error while loading. Please reload this page.
chore: add auto-add to engineer kanban workflow - #2
Merged
Conversation
@v1 is not a valid tag — action publishes full semver only. Pin to v1.0.2. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
LukasWodka
commented
Apr 24, 2026
ContributorAuthor
Re-triggering workflow after v1.0.2 pin. |
LukasWodka
commented
Apr 24, 2026
ContributorAuthor
Re-triggering after fixing the second workflow file. |
saadqbal
approved these changes
Apr 24, 2026
Uh oh!
There was an error while loading. Please reload this page.
2 tasks
3 tasks
LukasWodka pushed a commit
that referenced
this pull request
Apr 29, 2026
`gh api -F` performs type inference: digit-only strings are sent as JSON numbers. Two Status option IDs on project #2 are all-digit (`Done`=98236657, `Validation`=90729828), which made the `updateProjectV2ItemFieldValue` mutation reject them with `Variable $o of type String! was provided invalid value`. Switch the four `-F` flags on the mutation call to `-f` so they're always serialized as strings. The GraphQL types involved are `ID!`/`String!`, never numeric, so `-f` is correct for all four. Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
LukasWodka added a commit
that referenced
this pull request
Jul 28, 2026
add-to-kanban is fire-and-forget (one shot on 'opened', no retry), so a single dropped webhook leaves a permanent silent gap - e.g. tracebloc-engine#511 was never on the board, which blocked its dev->staging promotion. The weekly reconciler previously only fixed the Status of items already on the board; it now also sweeps every board-tracked repo for open + recently-merged PRs/issues with no project #2 entry and adds them in the right starting column (open PR -> Code review, draft -> In progress, issue -> Backlog, merged -> On dev/FR on staging/Prod by containment). Archived items count as present, so the daily archiver is never fought. Cap: aborts if > 60 look missing (scope bug guard). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This was referenced Jul 28, 2026
This was referenced Aug 10, 2026
LukasWodka added a commit
that referenced
this pull request
Aug 21, 2026
…rusted saadqbal on .github#295, and his framing is stronger than Bugbot Medium. Bugbot said a NEW literal in advance-deploy-env.yml would slip past. The fact is thirteen Status names are in that file RIGHT NOW, in `rank()`, and dropping it from WRITERS took all thirteen outside the check. The file documents its own failure mode: an unknown Status returns "" from rank(), the guard falls through to strict equality, and the card then BLOCKS every prod promotion carrying it. A read-side name that does not resolve breaks at least as loudly as a write-side one -- so "WRITERS is a write-side name" was technically defensible and wrong about what the check is for. That was the SECOND wrong removal on this PR; the router was the first. And the naive fix would have failed the check, which is worth knowing before someone tries it: twelve of the thirteen are live board options, and the thirteenth is `Staging (human review)` -- the phantom column this PR own headline finding identified. So, in order: 1. RETIRE THE SHIM. It ranked alongside `FR on staging` to keep the monotonic guard stable across the #1592 rename INSTANT, and that instant has passed -- measured against project #2. Keeping a shim for a completed rename is how this same file came to accept an override naming a nonexistent column. 2. Restore the file to WRITERS, where its remaining twelve get checked. Two hand-removals, two errors, is enough: `unlisted_namers()` derives against the tuple now. Any workflow naming a board column on a CODE line while absent from WRITERS is a finding, with a two-row exemption list carrying reasons. It found two on its first run, which is the argument for it existing: kanban-archive.yml:104 selects the three terminal columns to archive, and wip-limit-check.yml:47 defaults its column input to `Code review`. Neither writes a Status; neither name was checked. A rename would have left the archiver archiving nothing and the WIP check counting an empty column -- both indistinguishable from a quiet board. Three things the tests caught while doing this, all mine: the guard fired inside the stubbed-board cases and short-circuited main() (stubbed in run(), like cross_check, with the reason written down); my own cases got that stub because run() patches permanently, so they use kcc_fresh as the cross_check cases already did; and the paths-filter regex matched only CONSECUTIVE list items, so my interleaved comment truncated the block and produced a false "uncovered" that took two rounds to read as a parser artefact. 16/16, all 10 selftests green, ruff and actionlint clean. backend#2243 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
LukasWodka added a commit
that referenced
this pull request
Aug 21, 2026
…does not exist (#295) * fix(2243): one branch to Status mapping, and it surfaced a column that does not exist Two workflows decide a card Status from the branch a merge landed on, and they held THREE copies of the rule between them: advance-deploy-env had one and READ the per-repo `.kanban.yml` override; kanban-closure-router had two and IGNORED it. Both write Status, and a PR merged to develop fires both -- the router on pull_request closed, advance on the push -- so with a `.kanban.yml` present they would write DIFFERENT statuses for the same merge and run ordering decided which stuck. The router could not have honoured the override even in principle: it never checks the caller out. That is why the fix is one shared mapping rather than a second copy of the yq read -- the override has to be FETCHED. WHAT THIS FOUND, which is the part worth reading. `Staging (human review)` was an accepted override value in advance-deploy-env and is NOT a column on the board -- measured against project #2, whose Status options are Backlog, North Stars, Ready, In progress, Code review, On dev, Staging (agent review), FR on staging, Ready for prod, Prod, Done, Cancelled. A repo that had used it would have had its write rejected for naming a column that does not exist. Nothing caught it because the vocabulary lived in a shell `case` that kanban-columns-check.py never read. So kanban-columns-check now IMPORTS the mapping instead of regex-scraping two workflows for it. That is strictly stronger -- it reads the data structure rather than a rendering of it -- and it SHRINKS the regex surface rather than growing it. Both rewired workflows come out of WRITERS with the reason stated, since their literals are legitimately gone. The fold happens in `written_names`, not in `main`: the selftest substitutes `written_names` to control its input against a fake board, and folding into main silently widened what the selftest could not see. Caught by that selftest going red. A GUARD SO THE MAPPING CANNOT BE RE-COPIED. Its first version matched any `STATUS="On dev"` and flagged three innocent sites -- a sibling-merge holding state and two no-base-ref floors. Those are policy defaults for cases where there IS no branch, not copies of the mapping, and a guard that cannot tell the difference gets argued with and then switched off. Narrowed to a branch-keyed case arm, with both directions asserted. 22 selftest cases, 10/10 kanban-columns, all 10 .github selftests green, ruff clean, actionlint clean, make check green. backend#2243 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(2243): five findings, and two of them defeated the feature entirely Bugbot on .github#295. Two Highs, and both meant the override still never applied -- so the PR would have shipped a refactor with the bug intact. HIGH 1 -- PyYAML is not on the runner. `read_override` imported `yaml` lazily and treated ImportError exactly like a parse miss: a warning and an empty override. Neither rewired workflow installs it, so a real `.kanban.yml` would have been silently ignored and both writers would have kept the defaults -- failing open in the one environment that matters. Parsed with `yq` now, which is what advance-deploy-env used before this refactor, so it is a dependency this path already had. A present file with no parser REFUSES rather than defaulting: an unread override is not "no override". HIGH 2 -- the router `case` clobbered any non-stock override. Both sites called the mapper and then a `case` over the four stock branch names overwrote ANY other result with the On dev floor. So a `.kanban.yml` key for a branch outside those four was computed and discarded on the same run -- including the rfcs override this change exists to unblock. Keyed on whether the mapper ANSWERED now, which also removes the last branch-name list from that workflow. MEDIUM -- kanban-reconcile held the second copy, justified by "identical, deliberately" (.github#126). backend#2243 made that justification unsafe: once the router honours an override, an identical copy is no longer identical, and the weekly sweep would move an overridden card BACK onto a deploy column -- the slower one silently undoing the faster one, exactly as that comment predicted, through the mechanism it was written to prevent. It calls the mapper now; the Status-name to option-id case that remains is a different relation, not a second copy of the rule. MEDIUM -- a mapping-only PR never ran the board-name check, because `branch_status_map.py` was not in kanban-columns.yml `paths:` even though the check now imports it. Added, with its own selftest assertion: WRITERS does not name it (it is not a workflow), so the existing WRITERS-vs-paths case could never have covered it. MEDIUM -- I dropped kanban-closure-router from WRITERS on the claim its literals were gone. Only its BRANCH MAPPING moved; it still writes Cancelled, Done and On dev, and Cancelled/Done are not in the imported mapping -- so the conformance check could have gone green while those two named columns the board no longer has. Restored, with the reason. advance-deploy-env stays out because it now writes no literal at all, which is checkable rather than asserted. Also fixed a shell syntax break of my own: replacing the `case` heads left their arms and `esac` behind. actionlint caught it. 11/11 kanban-columns, 22/22 branch-status-map, all 10 .github selftests green, ruff and actionlint clean. backend#2243 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(2243): two Highs, both mine, both the same defect through a different door Bugbot on .github#295 round two. HIGH -- reconcile had no checkout. My own note said "branch_status_map.py is local: this job only ever runs in tracebloc/.github" -- true of the REPOSITORY and false of the WORKSPACE. A hosted runner starts empty, so the stage-derivation step would have failed on a missing file and taken the weekly backstop down mid-sweep. A comment that is confidently right about the wrong noun. HIGH -- read_override failed open on a fetch failure. It caught every `gh api` error and returned an empty map, identical to "no .kanban.yml". So a present override behind a 403, a 5xx or a rate limit was silently ignored and both writers applied the defaults: the exact override-ignore defect this change exists to close, reached by a different door. The parse and missing-`yq` paths already refused; this one did not. 404 is now the only failure that means "no override", matched on the message the way promote-repo.sh does it since `gh` exits non-zero for both. Everything else refuses. Four cases, and they assert `SystemExit` specifically rather than "it raised" -- a different exception would mean a different path, which is the rule this repo has on bare assertRaises. One of those cases failed on its first run for a reason worth keeping: the loop set the stub AFTER the call, so the 403 iteration ran against the 404 stub from the case above and reported a false failure. The test caught its own off-by-one. 26/26 branch-status-map, all 10 .github selftests green, ruff and actionlint clean. backend#2243 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(2243): the override was read from the wrong ref, and reconcile could not survive it Bugbot on .github#295, two Highs. HIGH -- `read_override` defaulted to the API HEAD, i.e. the repo DEFAULT branch, and no caller passed a ref. So an override present on `develop` but not yet on `main` was silently ignored by both writers. And that is a REGRESSION rather than a new limitation: advance-deploy-env previously read `.kanban.yml` off the checked-out PUSHED branch. My own docstring argued for the default on the grounds that a closed PR head may be gone -- true, and irrelevant, because every branch these callers map is a BASE branch that persists. Right reasoning, wrong input. All four call sites pass the ref now, and the selftest asserts it by reading the call sites: the failure is a MISSING argument, which no unit call can show. Verified non-vacuous by dropping one ref and watching it redden. HIGH -- the mapper call could take the weekly sweep down. Making read_override REFUSE on an unreadable `.kanban.yml` was right for a single card and wrong inside a loop: a bare command substitution under `set -euo pipefail` let ONE unreadable repo truncate the whole backstop mid-sweep, while the sibling closer lookups beside it already soft-fail. It soft-fails now and SKIPS the item rather than defaulting. Falling back to the default mapping would be the fail-open this PR removed, one layer out -- the router already moved that card correctly, and this sweep exists to fix misses, not to overrule with a guess. 33/33 branch-status-map, all 10 .github selftests green, actionlint and ruff clean. backend#2243 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(2243): the one arm that was silently empty while the docstring said otherwise Bugbot Medium, and saadqbal made the argument better than Bugbot did: this is not merely an inconsistency with the other arms, it CONTRADICTS the contract documented two lines above it. The docstring claimed "a malformed one is also empty, and SAYS SO on stderr". True of a YAML/JSON parse failure, which raises with ::error::. False of a `branch_status_map` that parses fine and is a list: no message, no exit code, {} returned. So the file had three refuse-loudly paths, one legitimate empty, and one that was silently empty while the docs said it announced itself. A documented contract that one path quietly breaks is worse than an undocumented one, because the next reader trusts it -- and I was that reader, twice, on this PR. The arm refuses now, and the docstring is a TABLE of all five outcomes rather than prose that drifted from three of them. The cases assert the five as a SET, not one at a time, because the defect was an inconsistency BETWEEN them. Plus the inverse -- a real mapping still comes back -- so the guard cannot degenerate into "refuse anything". 37/37, all 10 .github selftests green, ruff clean. backend#2243 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(2243): WRITERS was wrong twice, so it is checked now instead of trusted saadqbal on .github#295, and his framing is stronger than Bugbot Medium. Bugbot said a NEW literal in advance-deploy-env.yml would slip past. The fact is thirteen Status names are in that file RIGHT NOW, in `rank()`, and dropping it from WRITERS took all thirteen outside the check. The file documents its own failure mode: an unknown Status returns "" from rank(), the guard falls through to strict equality, and the card then BLOCKS every prod promotion carrying it. A read-side name that does not resolve breaks at least as loudly as a write-side one -- so "WRITERS is a write-side name" was technically defensible and wrong about what the check is for. That was the SECOND wrong removal on this PR; the router was the first. And the naive fix would have failed the check, which is worth knowing before someone tries it: twelve of the thirteen are live board options, and the thirteenth is `Staging (human review)` -- the phantom column this PR own headline finding identified. So, in order: 1. RETIRE THE SHIM. It ranked alongside `FR on staging` to keep the monotonic guard stable across the #1592 rename INSTANT, and that instant has passed -- measured against project #2. Keeping a shim for a completed rename is how this same file came to accept an override naming a nonexistent column. 2. Restore the file to WRITERS, where its remaining twelve get checked. Two hand-removals, two errors, is enough: `unlisted_namers()` derives against the tuple now. Any workflow naming a board column on a CODE line while absent from WRITERS is a finding, with a two-row exemption list carrying reasons. It found two on its first run, which is the argument for it existing: kanban-archive.yml:104 selects the three terminal columns to archive, and wip-limit-check.yml:47 defaults its column input to `Code review`. Neither writes a Status; neither name was checked. A rename would have left the archiver archiving nothing and the WIP check counting an empty column -- both indistinguishable from a quiet board. Three things the tests caught while doing this, all mine: the guard fired inside the stubbed-board cases and short-circuited main() (stubbed in run(), like cross_check, with the reason written down); my own cases got that stub because run() patches permanently, so they use kcc_fresh as the cross_check cases already did; and the paths-filter regex matched only CONSECUTIVE list items, so my interleaved comment truncated the block and produced a false "uncovered" that took two rounds to read as a parser artefact. 16/16, all 10 selftests green, ruff and actionlint clean. backend#2243 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(2243): restoring the file to WRITERS did not cover its rank() names Bugbot High + Medium on .github#295, and both are about claims I wrote one commit earlier. HIGH -- putting advance-deploy-env.yml back in WRITERS covered NONE of rank()s twelve names. `LITERAL` matches an assignment idiom and a `case` arm has no `=`; WRITERS membership also makes `unlisted_namers` SKIP the file. So all twelve stayed invisible -- and `Backlog`, `North Stars` and `Ready` were collected from nowhere at all -- while the comment I had just added claimed they were covered. Measured, not argued: 12 of 12 unattributed before, 0 after. `written_names` now collects every board column a WRITERS file NAMES, not only the ones it assigns, through one `names_in` helper that `unlisted_namers` also uses. Two copies of "does this file name a column" is the drift this file keeps finding in itself. MEDIUM -- the stale-exemption check the docstring promised did not exist. `unlisted_namers` said stale entries "are reported by the caller for the same reason mint-scope.py reports its own", and main() never diffed them. It does now, with three expiry conditions asserted separately because they are different facts: the file is gone, it joined WRITERS, or it names no column any more. Plus the inverse, so a live exemption is not a permanent red. The exempt dict moved to module level so both functions read ONE copy. AND FIXING THE HIGH NEARLY CREATED ITS TWIN. The staleness guard tested `if not found`, and `found` now has three contributors -- so case-arm names alone could have satisfied it while `LITERAL` matched nothing: the guard disarmed by the very change that broadened the collection. It is keyed on the assignment pass now, and I broke the pattern to watch it fire. 7 cases (23/23), all 10 selftests green, make check 41/41, ruff clean. backend#2243 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(2243): a refused override must not cost the router its write Bugbot High on .github#295, and it is NEW -- the old in-file `case` could not fail this way. `read_override` refuses on an unreadable `.kanban.yml`, and a bare command substitution under `set -euo pipefail` then exits the target step before any Status is published. The update step non-empty guard skips, and the project built-in "Item closed" automation sets `Cancelled` and archives shipped-via-parent work -- the .github#157 no-write path this same file documents, reached by a door I opened. And it fires exactly when the first repo adopts the override this change exists to unblock. THE POLICY DIFFERS BY CALLER, and that is the substance rather than a detail. reconcile SKIPS the item: it is a backstop that fixes misses, so writing a guessed column would overrule a router that already got it right. The router writes the DEFAULT: publishing nothing is strictly worse than ignoring an override for one run, because one is recoverable and the other leaves a card Cancelled. So `read_override` still refuses, and `--no-override` is how a caller asks for the answer it can safely fall back to. Neither caller gets a silent default -- the router logs a ::warning:: naming the repo, the branch and the reason. The fallback is proven to consult NOTHING by running it with `gh` removed from PATH, and the same call WITHOUT the flag is asserted to refuse -- so the flag is doing the work rather than a silent default. The call-site assertion failed on its own first run and the fix is the interesting part: it counted the new fallback calls and demanded a ref of them. A `--no-override` call consults no file by definition, so demanding a ref would demand the opposite of its purpose. It now distinguishes the two shapes, and additionally asserts the router has a fallback in BOTH arms while reconcile has NONE -- the per-caller policy, pinned. 47/47, all 10 selftests green, make check 41/41, actionlint and ruff clean. backend#2243 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix(2243): refusing has to be a WRITE where another system supplies the default saadqbal on .github#295, correcting my own fix from one commit ago -- and the qualifier he adds is the part worth keeping: "Refuse rather than guess" assumes that doing nothing is safe. At a decision point whose default is supplied by ANOTHER system, refusing has to be expressed AS A WRITE, not as an absence. My first fix wrote the DEFAULT MAPPING on an unreadable override. That denies the built-in Item-closed automation its opening, which was the point -- but it claims a promotion happened on a read that failed, which is exactly the "silently apply the default mapping" this PR removed everywhere else. The router writes the non-terminal HOLDING STATE now (`On dev`) and labels the card `override-unreadable` for the weekly pass, modelled on the sibling-merge arm three lines down, which keeps its Status write for precisely this reason. The holding state claims nothing: it is an explicit WE COULD NOT TELL. And it is still a write, because the built-in automation acts on the close independently of this workflow -- a loud red run does not protect the card, the automation wins the race. `--no-override` is gone from the router. It stays in the CLI because the flag itself is right and tested; nothing calls it today, which the selftest now asserts rather than leaving to be discovered. THE THREE CONSUMERS NO-WRITE PATHS ARE PINNED AS A SET, because that is what the finding is about -- three callers, three different correct answers, each for a stated reason: router writes the holding state + label; another system would decide reconcile skips the item; it fixes misses, nothing else acts on its silence advance no fallback; a push has no competing automation Reverting one arm to the old default-mapping fallback drops the suite to 48/2, so the distinction is checked rather than described. 50/50, all 10 selftests green, make check 41/41, actionlint clean. backend#2243 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
LukasWodka added a commit
that referenced
this pull request
Aug 22, 2026
…ploys nothing can say so (#304) * fix(2242): `Done` is a Status an override may name, so a repo that deploys nothing can say so `rfcs` ships no artifact and has no environment, so RFC-BACKEND-1405 D8 puts its merged work in `Done` -- completed, nothing deployed. backend#2242 is blocked on saying that: `ENV_FOR_STATUS` declared the five DEPLOY columns, and backend#2324 (correctly) made an undeclared Status a refusal -- so the semantically right override was the one shape the new guard rejected. The accept list is the set of Statuses a MERGE can legitimately mean, not the set of stages a deploy passes through. `Done` -> `none`, and `none` is a real option on the board's `Deploy environment` field (measured on project #2, 2026-08-22), so acceptance still carries an environment that AGREES with the Status. Three consumers were checked before adding it, because `Done` is terminal and nothing else in the table is: advance-deploy-env `rank()` already scores Done 11, above Prod -- the monotonic guard advances into it and nothing demotes out. kanban-closure-router its Done guard refuses to write Done OVER a deploy state; a merged PR's card sits in `Code review`, which `classify_column` returns `no` for, so the write lands. Both facts are already asserted by kanban-deploy-state-selftest.py. kanban-reconcile its weekly sweep pulls NON-TERMINAL columns only, so it never sees these cards. That last one was load-bearing and unasserted. Reconcile's `drift-to-prod` arm writes `Prod` for any merged PR whose sha reached the prod branch and consults no `.kanban.yml` at all -- deliberately, because `resolve_prod_branch` refuses to trust a repo-controlled file (D27-L4). The sweep filter is the only thing standing between that arm and every overridden card, so it is a machine check now: `branch-status-map-selftest.py` parses the filter out of the workflow and asserts no Status declaring `none` appears in it. Derived, not restated; fails loudly on a filter it cannot parse; and the predicate is exercised in both directions so a guard that returns [] for everything cannot pass as a satisfied invariant. Mutation-proved by adding "Done" to that list -- 1 failed, restored. Refs backend#2242, backend#2243, backend#2324 Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * fix(2242): the backstop can write `Done` too, and a check now says it must Bugbot (.github#304, Medium) was right, and my comment was wrong. It said kanban-reconcile's router-miss arm has "no option id for Done and skips" -- but `DONE_OPT` is resolved in that very step and already written by the arm beside it, so the skip was an omission, not a limit. The consequence is the shape this PR would otherwise have opened rather than found: widening the accept list without widening reconcile's `case "$DEST"` leaves exactly the repos the override exists for with no weekly backstop. A router miss parks the card in an active column, archive only takes terminal ones, and it stays there forever -- the invariant .github#127 fixed for every other mapping. Adding the arm is safe for the same reason writing DONE_OPT beside it is: the enclosing `_skip` guard has already run `classify_column "$COL"` and required `no`, so the card is in neither a deploy state nor an unplaceable column. AND THE INVARIANT IS A CHECK, keyed on the OPTION ID rather than on "every declared Status needs an arm" -- checking is what made that distinction (rule 8). Two declared Statuses have no arm on purpose: nothing writes `Staging (agent review)` yet (RFC-BACKEND-1552 D5, read-only until backend#1578) and `Ready for prod` is a human `/fr-pass` act (D6), and reconcile resolves no option id for either. So the real defect shape is "the job knows the id but cannot write it", and that is what `branch-status-map-selftest.py` now asserts: both the arms and the resolved ids are parsed out of the workflow, the parse fails closed with anchor assertions, and one predicate serves the assertion and both mutation directions. Mutation-proved by deleting the new arm (anchor verified applied, grep = 0): 173 passed, 1 failed, naming `Done`. Restored -> 174 passed, 0 failed. actionlint clean; `make check` green. Refs backend#2242 Co-authored-by: Claude Opus 5 <noreply@anthropic.com> * fix(2242): the option-id extractor stopped at the first `)`, so one arm was unguarded Bugbot (.github#304, High) again, and again right. `OPT_ID`'s non-greedy `\$\(opt(?:_either)?\s+(.+?)\)` stops at the FIRST `)` -- and the one argument list that matters contains one: $(opt_either 'Staging (human review)' 'FR on staging') OLD regex captured: ["'Staging (human review"] So `FR on staging` was never collected. The invariant went green with that arm deleted, and the anchor assertion did not notice, because `Done` -- which has no parens -- was present. An under-collecting extractor reporting a clean sweep of a SUBSET is exactly the shape `kanban-columns-check.py`'s `cross_check` exists for, reproduced one file over, one commit after I wrote a docstring about deriving rather than restating. Three changes, and the third is the one that would have caught it: * SCANNED, NOT REGEXED. `_arglist` walks the `$(...)` with a depth counter and is quote-aware, so a `)` inside a quoted column name is part of the name. An unterminated call returns "" -- a parse failure, not "no arguments". * THE ANCHOR NAMES THE HARD CASE. It now requires `FR on staging` and `Staging (human review)` -- reachable only through `opt_either` -- and >= 8 ids. An anchor a broken parser can pass is not an anchor. * THE MUTATION COVERS THAT PATH. Deleting the `FR on staging` arm is now a case. With only `Done` mutated, the guard was vacuous for every parenthesised name. Mutation-proved on the real workflow (anchor verified, grep = 0): deleting the `FR on staging` arm gives 172 passed, 3 failed, the primary assertion naming `FR on staging`. Restored -> 175 passed, 0 failed. `make check` green. Refs backend#2242 Co-authored-by: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This was referenced Aug 23, 2026
4 tasks
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
Adds a 14-line GitHub Actions workflow that auto-adds every new issue and pull request from this repo to the engineer kanban project:
→ https://github.com/orgs/tracebloc/projects/2
Fires on
issues: opened|reopened|transferredandpull_request: opened|reopened|ready_for_review. No effect on existing items.Why
GitHub Projects v2's built-in "Auto-add to project" workflow only supports one repo per project. This action-based approach is GitHub's recommended workaround to get cross-repo visibility on a single kanban.
Dependencies
Uses the org-level secret
PROJECTS_KANBAN_TOKEN(already configured withproject+reposcopes, visible to all repositories).Testing
After merge, open any new issue or PR and confirm it appears in the
Backlogcolumn of the kanban within a few seconds. If it doesn't, check the Actions tab for the workflow run.Related
One of ~18 identical PRs being rolled out across the tracebloc org so every repo's work lands on the shared engineer kanban.
🤖 Generated with Claude Code