fix(mage): make headless Claude actually edit files in CI - #60
Merged
Merged
Conversation
Confirmed on GitHub Actions: without a permission mode, claude replies "I don't have permission to write this file", edits nothing, and exits 0 -- so a regeneration reports success and produces an empty PR. Runs 33010304938 (fails) vs 33010790114 (passes) differ only by the flag. All 14 runClaude helpers also invoked bare `claude` with no --print, an interactive shape that has never run on a TTY-less runner. Both flags are gated on CI_REGENERATION so local runs are unchanged. Also adds the claudeAvailable gate the idiomatic tier was missing -- previously it was protected only by meta.yaml's shallow clone leaving HEAD~1 unresolvable, which would break silently if anyone set fetch-depth: 0.
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 free
to 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.
Fixes a defect confirmed on real GitHub Actions runs, not inferred. Without it, every automatic client regeneration would report success while producing an empty PR.
The defect
Two runs on
zz/claude-smoke, differing by exactly one flag:claude --print+ --permission-mode bypassPermissionsHeadless Claude answers and edits nothing without a permission mode, while still exiting 0. In this pipeline that is a silent green:
mage gen:allsucceeds, nothing changes, the PR is empty, and no check anywhere reports a problem.A second defect surfaced while scoping: all 14
runClaudehelpers invoked bareclaudewith no--print— an interactive shape that has never run on a TTY-less runner.What changed
1.
--printand--permission-mode bypassPermissions, gated onCI_REGENERATION— in all 14runClaudehelpers plus the root'srunClaudeOutput. Both flags come from one return statement, so they cannot get out of sync; a mismatch would reproduce a subtler version of the original silent failure.Local behavior is byte-for-byte unchanged.
claudeArgs()returnsnilwhenCI_REGENERATIONis unset, andexec.Command("claude", nil...)is exactlyexec.Command("claude"). The root keeps--printunconditional because its caller parses stdout.bypassPermissionsgrants unrestricted tool use, so the confinement matters: it is reachable only underCI_REGENERATION, which is set by exactly one workflow — the one holding Claude credentials, running on an ephemeral single-purpose container. The App backing that workflow has noWorkflows: write, so a regeneration cannot rewrite CI, and PR CI still gates the output.2. The
claudeAvailablegate the idiomatic tier was missing — the 7 proto Magefiles had it; the 7 idiomatic ones had none.This became load-bearing rather than tidy-up. Before change 1, an ungated idiomatic
runClaudein CI would merely error. After it, withCI_REGENERATIONset, it would succeed. Until now the idiomatic tier was protected only by an accident:meta.yaml's checkout is shallow, soHEAD~1does not resolve and every client skips withfatal: bad revision 'HEAD~1'. Anyone addingfetch-depth: 0tometa.yaml— for any unrelated reason — would have brokengen-nodiffwith no obvious connection to their change.3. Root unit tests for the new helper, covering both the unset and set branches via
t.Setenv.Verification
go vet -tags mageclean everywhere it can run;gofmtclean on all 16 touched filesrunClaudebodies remain byte-identical to one another — deliberate standalone copies across separate//go:build magemodules, where uniformity is what makes them auditableNote
proto-clients/spicedb-go-protoandspicedb-goeach hold two Go packages in one directory and cannot compile with-tags mageat all. Both predate this change and are checked withgofmtinstead.Still to validate
The smoke test that proved the flag is synthetic — it exercised
claude --printdirectly, not the mage code path. Real acceptance is a genuinemage gen:*run in CI once the regeneration workflow lands.