ReviewOS

also looking at this

stacks/buddy

feat(ci): let buddy see whose failure it is, and retry the ones worth retrying

#1410
Merged glennmichael123 wants to merge fix/ci-workflow-runs into main
16 files +1092 -14
9 days ago

Two defects with the same root cause: the provider contract could read one CI log, but could not see a branch's run history.

failsOnBase never fired

attemptFix has always known how to decline a pre-existing failure. The guard, its skip message and its tests all date from the original implementation — but nothing ever set the flag. No production caller computed it, so undefined was falsy on every run.

A pull request opened against a broken base branch had that breakage diagnosed, and sometimes repaired, as its own — attributing someone else's regression to the change, and hiding it where nobody was looking.

The flake branch retried nothing

It returned action: 'retry' and a report saying "re-running the failed job is likely to clear it", then re-ran nothing, because no provider could be asked to.

The capability

ciRuns, gating listWorkflowRuns and rerunWorkflowRun.

GitHubGitLabBitbucket
ciRuns✅✅ (jobs)❌

Separate from ciLogs for GitLab's sake — reading one run's log and reasoning about a branch's runs are the same API on GitHub and two different ones there. GitLab's listWorkflowRuns returns jobs, deliberately: a job is what getWorkflowRunLogs reads there, so returning pipeline ids would hand callers an id that looks interchangeable and is not.

Whose failure is it

Not "is the base red" — that would down tools on exactly the repositories that need the help most — but is it red in the same way. The newest run of each workflow on the base branch is classified from its own log and compared by kind.

  • Per workflow, so a workflow that has since gone green stops counting.
  • Capped at three log reads, because a base red in more than three distinct ways isn't worth optimising for.
  • An unreadable history reads as no evidence, not as breakage — a 403 on the list call cannot silently switch repairs off.

Comparing kinds is an approximation and errs deliberately. Two unrelated type errors both classify as type-error, so a real regression on a base that already fails to type-check reads as inherited. Buddy declines and explains, rather than committing a guess about code it has already been told is broken.

Retrying a flake

A transient failure needs no repair, only another go at the same commit. actions: write is already granted at the workflow level, so generated setups need no change.

Reported as fixed: false — the jobs were asked to run again and may still fail, and calling that fixed would be a guess dressed up as a result. The attempt counter from #1407 is what bounds the loop this opens.

What this deliberately does not claim

Re-running after a mechanical lock-file repair. A re-run executes against the commit it originally ran on, which is the broken one. The docs section explaining why those checks stay red stands — now with the second half of the reason written down, since the first half alone reads like an oversight rather than a constraint.

Tests

1929 → 1966. New: test/ci-base.test.ts (11), test/ci-runs.test.ts (17, real transport mapping against stubbed fetch), plus 9 in test/ci-fix.test.ts. ciRuns added to both conformance arrays.

Lint ✓ · typecheck ✓ · check:docs ✓ · 1966 pass / 0 fail

🤖 Generated with Claude Code

https://claude.ai/code/session_01UWouahBJT3msK7V2VDYY6T

16 changed files on the files tab, with 0 review threads.

Sign in to comment on this pull request.