Uh oh!
There was an error while loading. Please reload this page.
release-train: develop -> staging - #190
Merged
Merged
Conversation
Managed sync from tracebloc/.github/org-standards.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… reports on this PR (backend#1608)
…github#166)
The closing-issue advancement swallowed gh api failures into {} (a token/rate-limit/
permission error read as 'closes no issues' and silently skipped advancement, run
green) and capped closingIssuesReferences at 30 with no totalCount (a PR closing >30
silently truncated). Now: fail-closed (RUN_FAILED=1 + continue) on any lookup failure,
first:100 + totalCount surfacing on truncation, projectItems first:20.…lCount (Bugbot .github#181) The v1 fail-closed reddened every promotion carrying a PR_NUMBERS entry that isn't actually a PR (issue ref / bogus number). Capture output and skip QUIETLY on 'Could not resolve to a PullRequest'/Not Found, fail CLOSED only on a real infra failure. Add the projectItems totalCount check for parity with the closingIssuesReferences fix.
Managed sync from tracebloc/.github/org-standards.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…gbot .github#181) 'Not Found' is also what a scoped-down/expired token returns, so skipping it as benign re-opened the fail-open. Match only 'Could not resolve to a PullRequest'; any access/transport failure now falls through to RUN_FAILED.
…ub#181) Bare cis_out=$(...) under set -euo pipefail aborts the step on a non-zero substitution (High) -- restore the 'if ! cis_out=$(...)' form the per-issue lookup already uses. And printf|grep -q can return 141 via SIGPIPE under pipefail even on a match (Medium) -- use a here-string so grep's own exit status is what's tested.
…(backend#1630) Caught by Bugbot on the model-zoo staging mirror (model-zoo#131, held): the same promotion that deletes public-pii-gate-caller.yml also carries a CLAUDE.md asserting "gitleaks and the PII gate will catch it". The gate was retired earlier today by Lukas's decision (backend#1409); this line was not updated with it. That is a violation of this file's own rule, which I wrote: "If your change makes a statement in any CLAUDE.md, BUGBOT.md, or runbook false, update that file in the same PR." I retired the gate and left the canon claiming it protects - and this canon syncs into every repo's CLAUDE.md, so the false claim was queued to propagate to 19 repos. The replacement text does not just delete the reference; it names the resulting gap. gitleaks still covers secrets in CODE. Nothing now scans PR titles, descriptions or commit messages, which is exactly what the retired gate did and exactly where a customer name is easiest to leak on a public repo. A reader who only saw "gitleaks catches it" would reasonably assume the prose is covered too. Refs backend#1630, backend#1409.
…taging" (backend#1592 step 1) Step 1 of the three-step rename. Nothing changes behaviourally: this teaches all five reusables to prefer the NEW column name and accept the OLD one, so they work on both sides of the rename instant. Step 2 is the UI rename (Lukas), step 3 removes the fallback. WHY A FALLBACK RATHER THAN A SWAP. A rename cannot be done reads-before-writes. Adding a column can - teach everything to read the new name, then start writing it - but a rename makes the old name stop existing at a single instant, with no period where both are present. Every `select(.name=="FR on staging")` would resolve to null on the far side of that instant, and null resolution in these workflows means "abort" or, worse, "skip silently". So each resolver asks for the new name and falls back to the old, and each comparator accepts both. WHY THE UI, NOT THE API - and this is measured, not cautious. The ticket records that `updateProjectV2Field` replaces the whole option set and re-mints every ID: on 2026-08-05, adding ONE option preserved 0 of 11 option IDs and blanked 625 of 625 items, recoverable only because a name-keyed backup existed (#1576). The UI renames in place, preserving the option ID and every item's value. Step 2 is therefore deliberately a human action. Changed, by kind rather than by file: RESOLVERS (name -> option id), given a new->old fallback: advance-deploy-env.yml, kanban-closure-router.yml, kanban-reconcile.yml (via a new opt_either helper) COMPARATORS (status name read back from the board), now accepting both: advance-deploy-env.yml override map + rank(), fr-gate.yml rank(), kanban-reconcile.yml x3, kanban-closure-router.yml, fr-pass-comment.yml WRITE SITES, now naming the new column: kanban-closure-router.yml staging branch, advance-deploy-env.yml staging EXISTENCE PROBE: fr-pass-comment.yml resolves whichever staging-review column exists and keeps its loud failure for the case where NEITHER does - which is what that block was written for, and its comment already anticipated this rename. Both rank() functions score the two names identically, so the monotonic never-demote guard cannot see a rename as a backwards move. VERIFIED against live board data in both states, not just reasoned about: today (only the old name exists): opt_either -> d3caf1d4 after rename (name swapped): opt_either -> d3caf1d4 live id of "FR on staging": d3caf1d4 Same id either way, which is exactly what the in-place UI rename preserves. 18 literal references remain in comments and user-facing message strings. Those are correct as they stand today and become wrong only after step 2, so they are step 3's work rather than a partial edit now.
Removes the public PII gate in full, per Lukas (2026-08-10). The gate was
already inert — parked under backend#1409 (PII_DENYLIST was never provisioned,
so it failed closed on every public-repo PR while being a required check
nowhere) and its public callers were removed org-wide. This deletes what
remained:
- .github/workflows/public-pii-gate.yml (the reusable gate)
- .github/workflows/public-pii-gate-selftest.yml (its decision-table selftest)
- scripts/tests/pii-gate-selftest.sh (the selftest harness)
Also fixes the references that would otherwise dangle at deleted files:
- code-quality-caller.yml / code-quality.yml: drop pointers to the removed
gate + selftest
- version-bump-gate.yml: keep the override-ordering + grep-error lessons but
past-tense them ("the retired public-pii-gate")
- caller-drift.py: the visibility-mismatch finding no longer claims the pii
gate is required
Kept as history: the repo-inventory retirement tombstone and the
conformance-gate defect-class lesson (both reference backend#1409, not a live
file).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>docs(claude): sync org-standards block (backend#1602)
chore(ci): retire the public PII gate everywhere
docs(standards): the PII gate no longer exists, so stop promising it (backend#1630)
…ed-totalcount fix(kanban): fail-closed closing-issue lookups + totalCount (Bugbot .github#166)
…-fallback feat(kanban): resolve "Staging (human review)", fall back to "FR on staging" (backend#1592 step 1)
LukasWodka
commented
Aug 10, 2026
ContributorAuthor
bugbot run |
Uh oh!
There was an error while loading. Please reload this page.
…hipped (backend#1563 Layer 2) (#189) * feat(release): open a version bump PR when the declared version has shipped (backend#1563 Layer 2) The train reads version_file as the DECLARED NEXT RELEASE, cuts vX.Y.Z from it, and never writes it back. So the instant a release succeeds the file names the version that just shipped, and the NEXT prod hop hits version_preflight's "v$ver already exists and the delta touches published files" refusal. Every successful release arms the next one to fail (backend#1440). It stalled the tracebloc-py-package prod leg on 2026-08-05 and cost a release leg plus a hand diagnosis before anyone realised the train was right and the file was stale (backend#1561). Layer 1 (.github#172) cannot catch this case. It fails a PR whose author changed published files without bumping - but here nobody authored anything. The staleness is created BY the release. VERIFIED AGAINST LIVE STATE, and it found the condition already present: tracebloc-py-package develop declares 0.18.0 highest released final tag v0.18.0 -> already shipped; this would propose 0.18.0 -> 0.18.1 today So the pilot repo is sitting in exactly the state that stalls the next hop. WHAT IT DOES NOT DO. It never tags and never publishes. backend#1563 names "two competing publishers" as the failure to avoid, and the train owns the prod tag plus the release-managers token in the tag ruleset. This opens a PR; a human merges it. That is also why release-please was not adopted - we want its "open the bump PR" half and specifically not its "tag and publish" half. A PR rather than a push because develop is protected everywhere, and a push would need admin bypass, making this a second unreviewed writer to develop. Patch is a FLOOR, not a judgement: it unblocks the hop, and a human wanting a minor or major edits the PR. Computing the bump from commit types needs Conventional Commits, which #1563 sequences separately and no repo has adopted. Parsing is read_version's case table copied rule for rule, including the trailing `head -1` on every branch - `grep -oE` prints one line per MATCH, so a version line with a trailing comment otherwise yields a two-line value (release-train#41). A parser that disagreed with the train's would propose a bump the train then rejects. It refuses rather than guesses: a missing or unparseable version file, an unreadable tag list, an unreadable compare, a released version that is not X.Y.Z, or a write that does not read back as intended all exit non-zero with a named reason. The read-back check matters most - a sed that silently matched nothing would otherwise open a PR containing no change at all. Does nothing in the common cases: declared already ahead of every release, no publishable path touched since the release, or a bump PR already open. Caller for the py-package pilot follows once this reaches main - a caller pins @main, and passing inputs a @main callee does not declare kills the whole call with startup_failure. * fix: refuse at the compare file cap instead of reading it as "nothing published changed" Bugbot, .github#189. The compare endpoint caps `files` at 300 and does NOT paginate that array - `--paginate` walks commits, not files - and there is no truncation flag. So "no publish-path match" was not something this could assert, and asserting it exits 0 and SKIPS the bump PR, leaving exactly the stall this workflow exists to clear. A silent skip is the worst possible failure for this particular job. Layer 1 already refuses at that cap. The two were inconsistent, which is the thing I keep insisting on avoiding: a guard that disagrees with the gate it front-runs is worse than no guard, and here the two halves of the SAME ticket disagreed about what a truncated compare means. Three changes, all mirroring Layer 1 rather than inventing a second rule: - EMPTY file list is now a refusal. GitHub omits `files` entirely on very large compares, so empty cannot be told apart from "nothing changed", and zero files scanned is a malfunction rather than a clean answer. - ONE LINE PER FILE, tab-separated (filename + previous_filename), instead of one line per path. The cap check counts lines, and emitting two lines for a rename would make it refuse at fewer than 300 real files. Verified: three files including one rename counts as 3, not 4. - THE CAP ITSELF: 300 files with no match refuses. Note the asymmetry, which is why the check sits only on the no-match branch - a MATCH is sound on a truncated list, so a large compare that does match still gets a precise answer instead of a refusal. The rewrite also fixes a bug Bugbot did not mention: the old loop was `for f in $FILES`, which word-splits, so any path containing a space became two bogus entries. It now reads tab-separated fields, and matches the pre-rename path too - a file moved OUT of a published tree left the package, and the compare reports only its new path in the entry. Verified: actionlint clean, shellcheck clean at error level, `bash -n` clean on the extracted script, and the heredocs resolve to column 0 after YAML strips the block indentation. * fix: require a PAT for the bump PR — GITHUB_TOKEN-authored PRs get no checks Bugbot, .github#189, and it defeats the entire design rather than degrading it. Events authored with GITHUB_TOKEN do not start workflow runs. So a bump PR opened with the ambient token gets ZERO checks - and on a repo with required checks it is unmergeable forever. Measured on the pilot repo: tracebloc-py-package develop requires 6 checks: tests (3.11), tests (3.12), quality / gitleaks, quality / house-rules, quality / ruff, quality / shellcheck None of them would ever report. The PR would sit until someone merged it with admin bypass - which is exactly the thing this workflow opens a PR to avoid. It would have swapped "a push needing admin bypass" for "a PR needing admin bypass", while looking safer. The release train hit this first and its comment says why: "Bugbot only auto-reviews HUMAN-authored PRs, and settle/release now require a review". It uses RELEASE_TRAIN_PR_TOKEN. Same fix here, same reasoning, same secret available. - `pr-token` is a REQUIRED secret, deliberately not defaulted to github.token. A default would reintroduce the bug silently in any caller that forgot it. - checkout takes the PAT, so the credential the push uses belongs to it - what decides whether a push starts runs is the identity that performed it, not the commit author. - Only the two WRITE operations use it, named at their call sites the way promote-repo.sh does. Reads stay on the ambient token. - An empty token refuses UP FRONT, before the branch exists, rather than after - discovering it later leaves a pushed branch and no PR. Verified: actionlint clean, bash -n and shellcheck clean on the extracted script, and the workflow_call contract parses with pr-token required: true. * fix: an empty compare has two opposite causes — stop treating both as failure Bugbot, .github#189. I collapsed "nothing has landed since the release" and "GitHub omitted the file array" into one hard error. They are opposites: ahead_by == 0 $BASE holds nothing the tag does not. Right after a release that is the NORMAL state, and there is genuinely nothing to bump. files omitted dropped entirely on very large compares. Nothing was scanned, so no conclusion about published paths is available. Treating the first as a malfunction fails this job RED on an ordinary post-release day. Treating the second as clean skips a bump that is owed. The previous commit did the former, which is the noisier half of exactly the distinction I added the truncation guard to make. Fixed with `ahead_by` from the same response, confirmed by `files` being NULL (absent) rather than []. Verified on real compares: v0.18.0...v0.18.0 status=identical ahead_by=0 files=present(0) -> exits 0 quietly (previously: RED) v0.18.0...develop status=diverged ahead_by=3 files=present(9) -> proceeds, scans 9 files Note the ordering matters and the test found why: an `identical` compare returns files as a PRESENT but EMPTY array, so the absent-check would not have caught it. `ahead_by` has to be consulted first. The compare is now fetched ONCE and kept. Three questions are answered from it - ahead_by, files-present, and the file list itself - and re-fetching could observe three different states. Also handles ahead-by-real-commits-with-an-empty-files-array (empty or merge-only commits): nothing published can have changed, so that is a clean "nothing to do" rather than a refusal. * fix: refuse empty inputs — an empty publish-paths silently skips every bump Bugbot, .github#189. `required: true` on a workflow_call input still permits "". With an empty publish-paths, `for pat in $PUBLISH_PATHS` never iterates, MATCHED stays empty, and the job exits 0 as though nothing publishable changed - so a caller that forgot the input gets a workflow that runs, reports success, and never opens a bump. Demonstrated: PUBLISH_PATHS="" -> MATCHED=<empty> (exits 0, skips) PUBLISH_PATHS="tracebloc/* pyproject.toml" -> MATCHED=hit Layer 1 refuses the same case, in the same words: absent is never permission to skip the version guard (backend#1439). Mirrored here rather than invented. Guarded version-file and base-branch too, on the same reasoning. An empty version-file previously fell through to the `[ ! -f "" ]` check and refused with "does not exist", which is true but points the reader at the wrong problem. This is the FOURTH finding on this PR and the fourth of the same species: an empty or absent thing being read as a verified negative. Truncated compare read as clean. Empty compare read as failure. And now an empty input read as "nothing matched". The logic each was wrapped around was fine every time. * fix: read-only permissions — a write grant would hide the mistake pr-token guards Bugbot, .github#189. The permissions block granted contents:write and pull-requests:write to GITHUB_TOKEN, but nothing uses the ambient token to write: the branch push uses the credential checkout persisted from pr-token, and gh pr create names PR_TOKEN explicitly. Audited every call - the three ambient-token operations are matching-refs, compare and pr list, all reads. So the grant bought nothing and cost the fail-fast. A future edit that forgot to name PR_TOKEN would SUCCEED against the ambient token instead of taking a 403 - and that produces a PR authored by GITHUB_TOKEN, which starts no workflow runs, so its required checks never report and it can never merge. That is precisely the failure the pr-token guard was added to prevent two commits ago. A permissions block that lets it through quietly is worse than one that stops it loudly. It also contradicted the comment directly above it, which claims reads run on the ambient token for least privilege. The comment was right and the grant was not. Now contents:read + pull-requests:read, with the reasoning recorded at the block rather than in a commit message nobody will find. * fix: the idempotency probe refuses with a named reason like its siblings Bugbot, .github#189. The open-PR check used a bare assignment while matching-refs and compare both use `if ! var=$(...)` with an ::error::. Under set -e the bare form still fails the step, so this is about the OPERATOR rather than about safety: the run died at an unnamed line, breaking this file's own "every failed read exits with a named reason" contract. The reason matters here more than the consistency does. An unreadable PR list is specifically dangerous to guess at: assuming "none open" would open a SECOND bump PR alongside the existing one, and two PRs proposing different versions of the same file is worse than none. Audited the rest while in there - 3 guarded reads, 0 bare `gh` assignments left. * fix: measure the delta from the DECLARED version's tag, not the latest release Bugbot, .github#189. version_preflight refuses on "v$DECLARED already exists AND the delta touches published files" - it keys on the DECLARED version. I was measuring from v$LATEST, which asks a question the train never asks. The two are the same tag in the common case (declared == latest) and diverge exactly when the file has fallen BEHIND a release - reverted, or a stale branch. Measured on the pilot repo, which shows the size of the gap: DECLARED=0.18.0 (== latest) -> base v0.18.0, develop ahead_by=3 DECLARED=0.17.0 (behind) -> base v0.17.0, develop ahead_by=15 Five times the window. In the behind case the old code inspected 3 commits and could answer "nothing publishable changed" about a window the train is not looking at, while version_preflight tested 15 commits' worth of delta against v0.17.0 and stalled. Falls back to v$LATEST when the declared version is not itself tagged: it is <= LATEST and unreleased, so the newest thing that IS released is the honest baseline for "what changed since we last shipped". All eight compare messages now name the actual base rather than hardcoding v$LATEST, so the log says which tag the answer is about. * fix: derive the declared tag's existence from the guarded read, not a second probe Bugbot, .github#189 - and this one I introduced in the previous commit while fixing finding #7. That commit added `gh api git/ref/tags/v$DECLARED >/dev/null 2>&1` and treated ANY non-zero as "not tagged". A 404 means untagged; a rate limit or a 5xx means unknown. Collapsing them makes a transient failure silently select v$LATEST as the compare baseline - which is exactly the wrong-baseline fail-open that finding #7 was about. I closed a fail-open by adding a smaller one. The fix is not a better probe, it is no probe: $REFS from the matching-refs read above ALREADY lists every v* tag, and that read already refuses on failure. So the question is answered from data whose failure mode is handled, and there is no second failure mode to get wrong. Uses a here-string rather than `printf | grep -q`: under pipefail grep -q closes the pipe on its first match, the producer takes SIGPIPE, and a real hit returns 141 and reads as a miss. That is the .github#173 defect, and piping here would have reintroduced it in a place where a miss silently changes the baseline. Verified against real tag data: v0.18.0 and v0.17.0 resolve to themselves, v0.99.0 falls back. One matching-refs call total; the extra probe is gone.
…kanban card (#193) Bugbot .github#190 (Medium). The projectItems totalCount>20 guard only warned, so if an issue is on >20 projects and its engineering-kanban card sits beyond the first 20 we fetch, ITEM_ID comes back empty and the card is silently skipped as "not on project" while the run stays green — the exact silent-skip the guard was meant to close (the sibling closingIssuesReferences guard already fails closed). Move the truncation check into the not-found branch and set RUN_FAILED there: we only fail when the card was actually not found AND the list was truncated (pi_total>20), so a normal issue-on-<=20-projects still skips cleanly with no false failures. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
LukasWodka
commented
Aug 10, 2026
ContributorAuthor
bugbot run |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit b04c241. Configure here.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
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.

Automated promotion by the release train (RFC-0008 D14). Head is the train-managed
release-train/to-stagingbranch (a mirror ofdevelop), so it never collides with a human PR. Merged only when the fr-gate is green.Note
Medium Risk
Removing the public PII gate eliminates automated scanning of PR titles/bodies/commit messages on public repos; kanban and advance-deploy-env changes affect board state fleet-wide, though rename fallbacks limit breakage during the column transition.
Overview
This promotion batch updates org-wide GitHub automation in
tracebloc/.github: kanban workflows, release hygiene, and shared standards.Kanban column rename (backend#1592) — Staging merges and deploy advancement now target "Staging (human review)" instead of "FR on staging", with fallback resolution and matching rank() logic in
advance-deploy-env,fr-gate,fr-pass-comment,kanban-closure-router, andkanban-reconcileso automation works on either side of a one-shot board rename.advance-deploy-env— When promoting PRs, it also advances closing issues on the board (cross-repo), with fail-closed GraphQL handling (distinguish benign non-PR refs from real API failures), higher page limits, and guards when issue/project lists are truncated.Public PII gate removed — Deletes
public-pii-gate.yml, its selftest workflow, andscripts/tests/pii-gate-selftest.sh; trims related comments in code-quality callers.org-standards.mdnow states gitleaks covers secrets in code and PR prose on public repos is manual after retirement (backend#1409).New
version-bump-pr.yml— Reusable workflow (backend#1563 Layer 2) that opens achore/version-bumpPR when the declared version already shipped but publishable paths changed, using a PAT so CI runs on the PR; it does not tag or publish.Also adds
CLAUDE.mdwith synced org standards and minor comment/inventory tweaks (caller-drift,code-quality-caller).Reviewed by Cursor Bugbot for commit b04c241. Bugbot is set up for automated code reviews on this repo. Configure here.