Uh oh!
There was an error while loading. Please reload this page.
release-train: staging -> main - #234
Merged
Merged
Conversation
Four defects in one day shared a root cause: a fix that addressed the instance in front of the author and left the rest of its class behind. 1. a guard wrapped around load(checkout) while discover(root) stayed bare 2. an epoch-completeness helper used at two sites while a third did it inline with a raw non-distinct .count() 3. a core.hooksPath guard that canonicalised the DIRNAME of the path, not the path, so core.hooksPath=.. escapes it 4. a pre-push hook that probed for make but not for the ruff/pytest toolchain it then calls The rule names both shapes deliberately. 1 and 2 are other CALL SITES -- findable by grepping what you changed. 3 and 4 are single-site: another INPUT reaches the same guard, and no grep of the symbol will show you that. A rule that only says to check the code nearby catches half the class, which is how 3 and 4 shipped. Same week, from the other direction: a fleet-wide verification script accumulated five defects of exactly this kind, and every one surfaced by RUNNING it across all 16 repos rather than by reading the diff (tracebloc/release-train#48). Reviewing a predicate is not the same as enumerating what reaches it. Canon only. The sync (backend#1602) will report the fleet DRIFTED and can open the CLAUDE.md PRs; per-repo copies are never edited by hand. Refs backend#1646 Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
…oth (backend#1681) (#222) `evaluate_protection`'s exempt-staleness check was `if probe.classic_present:`. A branch protected SOLELY by a ruleset returns 404 from the classic endpoint, so it read as unprotected and its exemption stayed silently valid -- the guard would never notice that a role it believes is out of scope is in fact a real, protected branch. That is the two-systems defect the 20-line header of read_protection() exists to prevent, sitting in the one place that decides whether an exemption is still true. `probe.rulesets` is populated by the very same call; it was simply never consulted. The finding now also names WHICH layer covers the branch, so the reader is not sent to re-derive it. The gap survived because the coverage stopped one path short: `_ruleset_only` already existed in the selftest and was only ever pointed at the `required` path, never at `exempt`. It is now used for both. Verified: * selftest 111 -> 117 pass, 0 fail * MUTATION: restoring the old `classic_present`-only condition makes the new case FAIL (findings=[]), so the test genuinely covers the hole rather than passing either way * LIVE against all 20 repos: "No drift. Every repo read, every entry matched." -- no exemption was actually hiding behind this, so closing it costs nothing today and catches the next one. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…well as drift (backend#1608) (#223) Increment 3. The audit has always known every repo's state per family and only ever emitted the FAILURES, so a green run said "No drift" and nothing about what was actually covered. You could not tell a fleet that conforms from a fleet that was barely checked -- which is the same question this epic keeps finding the wrong answer to elsewhere (a caller that is present but advisory, a required check that never reports). Adds a per-repo x per-family table to the report: callers, copies, protection, rulesets, for all 20 repos, with the train flag alongside. Cells are deliberately three-valued: OK evaluated and matched N that many findings ? that family could not be READ `?` is the point. "Zero findings because we checked" and "zero findings because we never looked" must not render the same, and the whole guard exists to refuse that conflation -- so the screen refuses it too. Counted by DELTA around each family's block rather than by parsing the finding strings. Those strings are prose written for humans; keying a table off them would break the first time one is reworded. Verified: * LIVE, all 20 repos: renders 20 x 4, every cell OK, exit 0 * MUTATION: with an impossible required-check in the develop baseline, the protection column shows **1** for exactly the 16 train repos and OK for the 4 non-train ones whose develop role is exempt, exit 1. The screen shows red, and shows the SHAPE of the failure -- one family, one cohort. * selftest 116 -> 122, including that a clean row and an unread row do not render identically, and that an unreadable FAMILY marks only its own column * ruff --isolated --select E4,E7,E9,F -> All checks passed! Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…mplate check (backend#1680) (#224) Four instances on 2026-08-11, in TWO shapes -- and a rule aimed only at "neighbouring bugs" catches half of them: other CALL SITES * discover() left bare beside a wrapped load() * an epoch helper used at 2 sites while a 3rd did it inline other INPUTS TO THE SAME GUARD * the hooksPath guard canonicalised dirname, not the path, so `..` escaped it * the pre-push hook probed `make` but not the venv toolchain it calls The canon line names both shapes. The PR-template line is the part that does the work: it produces an artifact a reviewer can see is MISSING, where a prose rule produces nothing to look for. Why mechanical rather than remembered: the stuck-run detector built FROM today's lessons reproduced the same mistake -- it encoded the symptom (runner=NONE) instead of the condition (nothing progressing), and would have cancelled a healthy production deploy. The pattern survives being named, which is the whole argument for a checklist item over a paragraph. Cost, stated rather than discovered: editing the canon drifts all 19 repos and needs a sync fan-out. That is the third such edit this week and is exactly the treadmill worth deciding about (auto-merge on green, or batching) -- tracked on backend#1680. Verified: standards-sync selftest 27 checks / 0 failed; the report still splices the block cleanly against all 19 targets. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…s passed (backend#1680) (#225) .github had no `.cursor/BUGBOT.md` — one of five repos without one — despite being public and holding the reusables ~120 callers consume at @main, plus the conformance contract itself. Written from this repo's actual defect history rather than generic advice. Nearly every real finding here is one shape: a guard that reports success it did not verify. The guide names the variants seen in this repo (empty response read as 'nothing found', `|| echo 0`, a grep in a pipefail pipeline returning 141, a required check that is path-filtered so it can never report, a soft-fail default on a required check), plus the two-protection-systems trap and the @main caller/callee ordering rule that turns a mistimed input into startup_failure. Also records the non-issues that were being re-raised: retired pii-gate contexts, the deliberate `strict: false`, and the long incident comments that are load-bearing rather than verbose. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
#220 and #224 both landed the same rule one minute apart, so org-standards.md carried two near-identical 'Fix the class, not the instance' bullets. Because this file is the synced canon, the duplicate would propagate into every repo's CLAUDE.md (Bugbot, on the develop->staging mirror #226, which it blocked). Keeps the line-25 wording: the neighbouring bullets all use unbolded lead-ins, and the removed one was the only bullet in the file starting with bold. It also sat between the post-PR and recurring-finding bullets, splitting that flow. Verified: one occurrence of the rule remains, zero bullets start with bold. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…pipe to head (#228) `jq ... /tmp/gitleaks.json | head -50` builds the gitleaks findings table for the step summary. The step runs `set -uo pipefail`, and Actions launches every `run:` block as `bash -e {0}` — so errexit is ON despite the `set -uo` reading as though it were off. `head` closes the pipe after row 50, jq takes SIGPIPE once the output no longer fits the pipe buffer, pipefail turns that into a pipeline status of 141, and errexit aborts the step mid-summary. The short table is the least of it. The abort happens before the `::error::`/`::warning::` annotations are emitted and before the soft-fail `exit 0`, so a repo configured `soft-fail: true` HARD-fails instead of reporting — and the summary loses the "rotate it first" remediation guidance entirely. Capture the rows into a variable and slice the capture with a here-string, so jq always runs to completion and the exit status is its own. The emit is guarded on a non-empty capture, because `head <<<""` would print one blank line where the pipeline printed nothing. Latent, not live: it needs roughly 700+ findings to fill a 64K pipe buffer, and gitleaks findings normally sit at zero. Measured on darwin it tips from ~250 findings (~24K of rows), nondeterministically right at the boundary because it is a race. Verified against the real step body extracted from this workflow: at 0/1/50/ 51/200 findings old and new produce byte-identical summaries, the same exit code and the same annotation count; at 1000 findings old returns 141 with 0 annotations, new returns 0 with all 1000. actionlint 1.7.12 (the version CI pins) reports zero findings. Refs: backend#1778 Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
release-train: develop -> staging
release-train: develop -> staging
* ci(gate): fail a PR that declares itself blocked (backend#1729)
data-ingestors#468 was titled "... (D10) [blocked on S2]". It collected three
approvals and was merged by its author 105 seconds after S2 merged. The author
waited for the blocker they had written down -- but "S2 merged" was never the
real precondition, and nothing could tell the difference. dev and staging
ingestion broke within hours and stayed broken for a day (backend#1752).
The dependency was recorded as prose, in a place nothing reads. This makes that
sentence a merge gate: a PR whose title or labels say it is blocked fails a
check. Two open PRs carry the shape today -- client#490 ("HOLD until v0.8.0
image") and client-runtime#192 ("DO NOT MERGE").
What it does not do: decide whether the blocker is genuinely resolved. #468's
author satisfied the sentence they wrote and the real precondition was still
unmet. The claim is only that clearing the marker is a deliberate act.
The matching is the entire risk surface, so it lives in a tested script rather
than a YAML heredoc. Precision over recall: `unblocked`, `unblocks`, `blocker`,
`threshold`, `holder` and `wipe` must not fire, or the gate gets called noise
and switched off -- the failure mode house-rules.sh names in its own notes.
No callers in this PR, deliberately. The reusable lands first so the matching
can be reviewed alone; repo-inventory.yml records it `exempt` everywhere with
the rollout named, because this file's own header forbids adding a caller and
flipping it to `required` in one PR. Arming is a briefing decision: two live
PRs go red the moment it is on, which is the gate working.
Verified: marker selftest 22 checks green, including the GITHUB_EVENT_PATH path
the workflow actually uses; both event-path assertions mutation-checked (drop
the title / drop the labels -> each fails). caller-drift selftest 116/0.
load_inventory + check_source_reusables pass on the edited inventory, and fail
as intended when the reusables row is removed. actionlint clean.
* fix(gate): anchor the WIP marker — measurement found a real false positive
Ran the matcher over 588 merged PR titles from ten tracebloc repos rather than
trusting the table I had imagined. It produced exactly one false positive:
chore(ci): retire the WIP-limit nudge
and it is the worst kind, because "WIP limit" is a domain term in THIS org --
wip-limit-check.yml is one of our own reusables, so the phrase recurs. The
(?<![a-z])/(?![a-z]) guards do not help: the character after "WIP" is a hyphen.
That is precisely the failure this gate cannot afford. A check that fires on a
normal chore PR gets called noise and switched off, and then catches nothing
ever again.
WIP-as-a-marker is a PREFIX convention (`WIP: ...`, `[WIP] ...`); WIP mid-title
is the noun. Anchoring to the start keeps every real marker and drops the false
positive. Over the same 588 titles the matcher now blocks exactly one:
data-ingestors#468 -- the PR this gate exists for.
Both new cases mutation-checked: un-anchoring `wip` fails 2 assertions.
* fix(blocked-gate): fail closed on a payload with no pull_request; minimal perms; label events (Bugbot #229)
- blocked-marker.py: a GITHUB_EVENT_PATH payload with no pull_request object now
refuses (exit 2) instead of returning an empty title -> 'not blocked' -> exit
0. A non-PR or malformed event was a silent fail-OPEN; it now fails closed like
the no-event-path branch. Selftest case added.
- blocked-gate.yml: drop 'pull-requests: read' — the script reads
GITHUB_EVENT_PATH, never the API, so the wider grant would exceed a minimal
contents:read caller and fail the reusable at startup.
- Document that callers must trigger on labeled/unlabeled too, or the label half
of the gate never re-runs until the next synchronize.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>Bumps this repo's own add-to-kanban caller, and flips repo-inventory.yml so v2.0.0 is the fleet norm rather than the drift. v1.0.2 declares `using: node20`. Node 20 is deprecated on GitHub-hosted runners, so GitHub force-runs this action on Node 24 today and prints the deprecation notice on every run -- including the 542 that succeeded. That fallback is temporary; when it is withdrawn every repo still on v1.0.2 breaks at once. v2.0.0 declares node24 and needs no fallback. Not an outage fix: re-running the reported failed job unchanged on the v1.0.2 pin succeeded, so that failure was a transient TLS condition, not a runtime incompatibility. cli and release-train have run this exact v2.0.0 pin successfully (12/12 recent runs). v2.0.0 is input-compatible: it declares project-url, github-token, labeled and label-operator; our callers pass the first two. Inventory changes: * cli's add-to-kanban.yml goes divergent -> required. Its copy now matches the canonical one byte-for-byte (blob 603751a), and leaving it divergent would fail the guard with "marked divergent but now matches". * release-train's entry stays divergent -- its header comment still differs -- but the half of its reason claiming the v2.0.0 pin is drift is now false, so the reason is corrected to name the header alone. * stale-backlog.yml's divergent entries and devex-bootstrap's exemption are untouched. README.md told a new repo to pin v1.0.2, which would reintroduce the node20 pin this sweep removes. This must merge AFTER the other 16 caller PRs: copies are compared by blob id against the canonical file in the checkout, so until they land the audit correctly reports 16 drifted copies. Documented awkwardness, fail-closed direction, resolves itself. Refs: tracebloc/backend#1816 Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
release-train: develop -> staging
LukasWodka
commented
Aug 12, 2026
ContributorAuthor
bugbot run |
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 f45fc9f. Configure here.
Uh oh!
There was an error while loading. Please reload this page.
7 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.
Automated promotion by the release train (RFC-0008 D14). Head is the train-managed
release-train/to-mainbranch (a mirror ofstaging), so it never collides with a human PR. Merged only when the fr-gate is green.Note
Medium Risk
New org-wide reusable workflow contract and inventory changes affect every repo once callers land; fixes touch fleet conformance auditing and shared code-quality behavior at scale.
Overview
Adds a blocked merge gate (reusable
blocked-gate.yml+blocked-marker.py) that fails PRs with the orgblockedlabel or title markers like[blocked on …],DO NOT MERGE, orHOLD until …, with a path-filtered selftest biased toward false positives. The reusable is registered inrepo-inventory.ymlbut exempt fleet-wide until a follow-up wires callers (backend#1729).Conformance and quality fixes:
caller-drift.pynow treats ruleset-only branches as protected when probing staleexemptentries (backend#1681) and prints a per-repo conformance matrix; gitleaks summary generation avoidsjq | headSIGPIPE sosoft-fail: truecannot hard-fail on large finding sets. Fleet pin:add-to-kanbanmoves toactions/add-to-project@v2.0.0;cli’s kanban copy is no longer marked divergent for that pin.Docs/process: new
.cursor/BUGBOT.md, org “fix the class” guidance, PR template Sibling check item, README pin note.Reviewed by Cursor Bugbot for commit f45fc9f. Bugbot is set up for automated code reviews on this repo. Configure here.