Skip to content

release-train: staging -> main - #401

Open
LukasWodka wants to merge 3 commits into
mainfrom
release-train/to-main
Open

release-train: staging -> main#401
LukasWodka wants to merge 3 commits into
mainfrom
release-train/to-main

Conversation

@LukasWodka

@LukasWodka LukasWodka commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Automated promotion by the release train (RFC-0008 D14). Head is the train-managed release-train/to-main branch (a mirror of staging), so it never collides with a human PR. Merged only when the fr-gate is green.


Note

Medium Risk
Changes affect daily kanban CI gates and branch-attribution tooling used before destructive git operations; failures are mostly fail-closed, but incorrect pagination or baseline logic could block workflows or mis-attribute branches until fixed.

Overview
Fixes two operational bugs that made automation look healthy while misbehaving, plus test harness updates to lock the behavior in.

Kanban archive baseline recall (backend#2802) sets GH_REPO on the recall step so gh run download can resolve the repository without a checkout (listing worked via URL; download failed on “not a git repository”). Download failures now capture stderr in prev.stderr and surface it in prev.error instead of swallowing it with 2>&1, without writing stderr into prev.error on success (which would falsely mark the baseline unreadable).

branch_owner.py (backend#2972) replaces capped gh pr list --limit 1000 with a fully paginated GraphQL pull-request read validated against totalCount (duplicate-page and mid-read count checks). Incomplete reads exit non-zero with a marked refusal instead of printing hundreds of unattributable rows. gh repo view for default branch and repo identity now passes the repo positionally because --repo is invalid on that subcommand. Selftests and mutation suites were extended for GH_REPO, stderr handling, pagination, $endCursor, and the incomplete-list refusal path.

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

LukasWodka and others added 3 commits September 1, 2026 16:33
…ame itself (backend#2802) (#397)

* fix(kanban): give the baseline download a repo, and let its failure name itself (backend#2802)

kanban-archive.yml has been red on every scheduled run while ARCHIVING PERFECTLY --
today: 72 archived, 0 failed, then a non-zero exit. The failure is one missing env
var, and the reason it took a reproduction to find is a second defect in the same
step.

THE CAUSE. `gh run download` takes no `--repo`, and this job has NO checkout (its
steps are all API calls; it needs no source). So gh resolved the repository from the
git remote, found no `.git`, and died BEFORE making any API call. That is exactly why
the artifacts LISTING in the same step succeeded -- it puts the repo in the URL path --
while the fetch failed.

Reproduced outside Actions, gh 2.98.0:

    $ cd /tmp/empty && gh run download 33393320354 -n board-baseline
    failed to run git: fatal: not a git repository (or any of the parent directories)
    $ GH_REPO=tracebloc/.github gh run download 33393320354 -n board-baseline -D prev
    $ cat prev/board.total
    574

574 is the baseline the job could not read. `gh` does NOT read
`GITHUB_REPOSITORY` -- tested -- so being on a runner does not supply it. `GH_REPO`
is the variable it does read, and it is narrower than adding a checkout to a job that
needs no source.

RULED OUT RATHER THAN GUESSED PAST: not retention (`retention-days: 30`; the
artifact is 142 bytes, created 2026-08-31, `expired: false`); not permissions
(`actions: read` is set and the same token succeeded on the listing in the same
step); not the download-artifact v4 cross-run restriction (there is no
`actions/download-artifact` in this file at all).

AND THE SECOND DEFECT, which is why five red runs were undiagnosable from their own
logs: `>/dev/null 2>&1` on the download threw away the only line that said why, and
the refusal below then CLOBBERED `prev.error` with a generic summary. Now stderr is
captured to `prev.error` and the summary APPENDS the tool's own words rather than
overwriting them. A refusal that cannot say what it saw sends the next reader to
reproduce it by hand, which is what happened here.

NOT A DUPLICATE: backend#2802 and backend#2903 are both closed, and #2903 is a
different defect (a `per_page=100` listing treated as complete -- the class tracked
by the open #2944). Neither describes this cause. The download path itself landed
2026-08-30 in .github#383.

GUARDED. `archive-baseline-selftest.py` now asserts the recall step sets `GH_REPO`
from `github.repository` and captures the download's stderr. Both mutation-proved
against the live file: removing `GH_REPO` -> "does not set \`GH_REPO\`";
restoring `2>&1` -> "not capture stderr". Full selftest green either side --
10 cross-run + 10 identity + 6 completeness cases + the detector anchors.

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

* test(kanban): re-anchor the stale download-failure mutation (backend#2802)

The fix in the previous commit rewrote the download-failed branch to capture gh
stderr and append to prev.error, so archive-baseline-mutations.py own anchor for
that branch matched 0 times.

THE HARNESS CAUGHT IT ITSELF and refused, rather than reporting 0 uncaught about a
premise nobody typed -- which is why this is a re-anchor and not a discovery. An
inert mutation is indistinguishable from good coverage in a log, and that guard is
the reason this commit exists.

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

* test(kanban): anchor the whole printf, not its first line (backend#2802)

The re-anchor in the previous commit applied but changed nothing: it replaced only
the printf FIRST line, leaving the continuation ("$rid" ... > prev.error) standing
as its own command -- so prev.error was still written and the mutation went
UNCAUGHT rather than inert. Two different failures with the same symptom in a log,
and the harness distinguished them: "anchor matched 0 times" first, then "the suite
passed with this broken".

Anchoring both lines makes the mutation actually remove the write.

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

* fix(kanban): capture the download stderr WITHOUT dirtying the unreadable sentinel (backend#2802)

Bugbot High on my own fix, and it is right about the coupling. The previous commit
sent `gh run download` stderr straight into `prev.error` -- but the assert step
treats ANY non-empty `prev.error` as an unreadable baseline (`if [ -s prev.error ]`
at :724). So a SUCCESSFUL download that printed anything at all would mark the
baseline unreadable and keep the job red after a good archive: the exact failure
this PR exists to fix, arriving from the other side.

MEASURED, AND THE FINDING AS DESCRIBED DOES NOT REPRODUCE: `gh run download` writes
0 bytes to stderr on success on a non-TTY (tested, gh 2.98.0; a runner is also
non-TTY). So no run would have gone red today.

FIXED ANYWAY, because the coupling is the defect rather than the current byte count.
One deprecation notice on stderr in a future gh is all it takes, and this file is
full of exactly that shape -- a sentinel that means "could not read" quietly
acquiring a second meaning. stderr now goes to `prev.stderr` and only the FAILURE
branch promotes it into `prev.error`, so the sentinel has one writer and one
meaning.

The stale comment claiming stderr "lands in prev.error" is corrected in the same
commit -- it would otherwise describe the shape this change just removed.

Selftest extended both ways: the download must capture into `prev.stderr`, and it
must NOT write into `prev.error`. Two mutations, both reddening -- restore the
straight-into-sentinel write; drop the capture entirely.

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
…e out loud (backend#2972) (#398)

`branch_owner.py` asked `gh pr list --limit 1000`. `tracebloc/backend` has 1418
pull requests, so the seam failed closed on the org's LARGEST repo -- the one
carrying 62 of the org's 99 stale branches -- and `main` printed
`108 branch(es): 0 attributed, 108 unattributable` and exited 0. That line is
character-for-character what a genuine "nobody can be named here" answer looks
like, so a reader who trusted the tool concluded every branch in the repo was
unowned.

A bigger number would only move the date this happens again, so the read now
PAGES TO THE END (`gh api graphql --paginate --slurp`, cursor variable named
`$endCursor` because gh injects into that name and no other) and asks the
repository for its own `totalCount` on the same connection. "Did the read
finish?" is then two measured numbers compared, not a threshold anybody picked:
rows == totalCount, or a refusal naming both counts and the shortfall.

The fail-closed backstop survives and gets sharper. A short read, a page that
came back twice (what a broken cursor looks like), a count that disagrees with
itself between pages, and a GraphQL `errors[]` at HTTP 200 are all refusals --
carrying a marker `main` matches on, so it prints no rows and exits 2 rather
than tallying a refusal into the shape of data. The other PR-list problems still
print their rows, with the one reason carried into the summary line.

Also, found while verifying: `gh repo view` has NO `--repo` flag -- it takes the
repository positionally, and the flag form exits 1 with `unknown flag: --repo`.
`default_branch()` built the flag form, so the authoritative lookup was
unreachable on every `--repo` run, the answer silently came from `origin/HEAD`
(a clone-time cache), and the first-commit signal was withheld for every branch
while the message blamed a remote that was never asked. Measured on
`tracebloc/backend`: 88 of 108 attributed with the paging fix alone, 102 of 108
with both.

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@LukasWodka

Copy link
Copy Markdown
Contributor Author

bugbot run

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ 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 885df27. Configure here.

@tracebloc-release-train tracebloc-release-train Bot added the gate-nudge Toggled by the release train to (re-)fire the fr-gate label Sep 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gate-nudge Toggled by the release train to (re-)fire the fr-gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant