The mechanical repair reported:
Regenerated the lock file and pushed the result. No model was needed.
Nothing pushed anything. The callback rewrote the file in the workspace, the job ended, and the workspace went with it — so the one repair Buddy can make without a model, and the one it is most confident about, never reached a branch.
That is the worst shape a report can take: confident, specific and wrong.
Reporting what actually happened
Three outcomes, now distinct:
| Result | Report | fixed |
|---|---|---|
| Regenerated and pushed | "…and pushed the result. No model was needed." | true |
| Regenerated, push failed | "…but could not push it to the branch. The change is in the workspace only, so the run will still be red." | false |
| Install failed | "Tried to regenerate the lock file, but the install did not succeed." | false |
fixed is true only for the first — a file rewritten in a discarded workspace has repaired nothing.
Landing it
commitAndPush in src/utils/git.ts. Deliberately not GitProvider.commitChanges, which does git reset --hard origin/<base> + git clean -fd: correct for a dependency update Buddy owns end to end, catastrophic on a branch carrying someone else's commits. The fix-ci job has already checked the branch out with credentials, so a local commit is sufficient and safe.
Staging nothing counts as success with nothing to record — an identical regenerated file is not a failure.
A tested policy I nearly broke
I first changed the checkout to secrets.BUDDY_TOKEN || secrets.GITHUB_TOKEN, so the push would re-trigger CI and actually turn the build green. The suite stopped me:
cli-setup-extended.test.ts: GITHUB_TOKEN is primary for bot attribution; BUDDY_TOKEN rides alongside it for elevated scopes. The||pattern breaks that.
Five tests, and they are right — a PAT push would attribute Buddy's commits to whoever owns the token. Reverted.
So the trade stands and is now documented rather than implied away: the fix lands, but the checks still need re-running, because GitHub does not start a workflow run for a GITHUB_TOKEN push. Making that automatic without breaking attribution wants its own change — re-running the failed run via the API, guarded by the attempt counter from #1407.
--dry-run was not dry
The callback was passed unconditionally and never consulted the flag, so buddy fix-ci --dry-run rewrote lock files on disk while reporting it had changed nothing. attemptFix now takes the flag and reports the repair it would make without calling the callback at all.
Still open
failsOnBase remains unwired — deciding it needs a way to find the base branch's run, and the provider exposes only getWorkflowRunLogs(runId).
Verification
- 1909 pass / 0 fail (+4), lint clean, typecheck clean
check:docsgreen: 75 pages, 33 commands, 221 config keys- Docs site builds: 73 pages
🤖 Generated with Claude Code