Repository navigation
Send Claude back once when a turn ends with unverified code - #3
Merged
Merged
Conversation
cargo fmt --check failed on 8 spots in ai.rs and lib.rs. This commit only reformats them, so the next one can add the check to pnpm verify.
pnpm verify runs every check: both TypeScript projects, vitest, the web build, and cargo fmt, clippy, and tests. It takes about 5 seconds warm. It records a hash of the tree in .git/undertext/ only when every check passes and no file changed during the run, so a later hook can tell whether a change was verified. A docs-only change needs no run. A change to CI files alone is never recorded. - CLAUDE.md says what done means and to run pnpm verify. - .github/workflows/verify.yml runs it on macOS for pushes to main and pull requests. The actions are pinned by commit. - docs/agents.md says what is checked, what is only asked, and the known limits. - scripts/verify.test.mjs runs copies of the scripts in throwaway repositories. - .gitignore now covers .claude/settings.local.json. Claude Code writes it, and a change to it would void a pass.
Two Claude Code hooks in .claude/settings.json run scripts/guard.mjs. When a turn starts, it records a hash of every file. When Claude stops, it compares: if the turn changed code (not only docs) that pnpm verify hasn't passed, it sends Claude back once, naming the files. The second stop goes through, so Claude can stop on purpose and say what is not verified. Turns that changed nothing, changed only docs, or undid their edits pass. Any error lets the turn through with a message. The same file has ask rules for git push, gh pr merge, and --no-verify, so Claude Code asks before it pushes or merges, also in auto mode. - The guard relies on its own record for "once", not on stop_hook_active, which any Stop hook sets. - Records live in the shared git folder, one per session, with the checkout the turn started in. - scripts/guard.test.mjs runs copies of the guard in throwaway repositories. The helpers moved from verify.test.mjs to test-repo.mjs; no test changed. - Proven in headless Claude Code sessions: an unverified edit was sent back once, and push and merge were refused even when allowed.
This was referenced Oct 25, 2026
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.
Stack, 2 of 4. Based on #2 (
agents/1-verify). Merge after it.What it adds
.claude/settings.jsonrunscripts/guard.mjs. When a turn starts, it records a hash of every file. When Claude stops, if the turn changed code thatpnpm verifyhasn't passed, it sends Claude back once, naming the files. The second stop goes through, so Claude can stop on purpose and say what's unverified. Turns that changed nothing, changed only docs, or undid their edits pass. Any error lets the turn through with a message.askrules forgit push,gh pr mergeand--no-verify. They prompt even in auto mode. They're a speed bump, not a lock.Evidence
scripts/guard.test.mjs, in throwaway repositories, each seen failing against a planted bug.git push, a dry-run push andgh pr mergewere refused even with--allowedToolsallowing them. A control run without the project settings ran them.stop_hook_activeis set by any Stop hook, which would have turned the gate off under loop plugins. The guard now uses its own once-per-turn record.Known limits
Claude Code only. Two sessions in one checkout see each other's edits. A turn that moves to another checkout isn't checked, and says so.