fix(rules): warn when a diverged checkout will strand a frustrations entry - #118
Merged
Conversation
…entry (AEAB-18)
Four frustration entries were appended on 2026-08-17 to ~/Developer/amux, a
checkout ~1000 commits behind with unpushed local commits and an hourly sync job
that had failed 80+ runs. That copy held 25 entries; origin held 124. The appends
SUCCEEDED — no error, nothing to notice — and reached nobody.
`.claude/rules/frustrations.md` says to append to "frustrations.md at the repo
root", and CLAUDE.md lives in that same stale checkout. So for a session working
there — the documented working directory — the instruction is correct as written
and produces an invisible write.
This is NOT the same defect as the divergence itself. A stale checkout announces
itself the moment you pull. A diverged one swallows WRITES instead of blocking a
read, so the whole point of the file is quietly lost: one frustration is a
complaint and a cluster is an argument, and a cluster only forms in the file
everyone reads.
Two halves, because either alone is weak:
1. The rule now says "the repo root" means the checkout that can actually PUSH,
with the two rev-list commands to check, and the divergence condition spelled
out.
2. The SessionStart hook gains a third axis and says it at the one moment it
matters. A doc that only asks you to remember is what ethos rule 6 warns
about; the hook is where a session already looks.
Fires only on DIVERGENCE (ahead AND behind), never on merely-behind — that is
recoverable by a pull and a push still reaches origin, so warning there would be
noise. The hook's own header names silence-when-current as the reason it gets
read at all, and a banner that always fires is one nobody reads.
Verified against the incident's own artifact, not a constructed case. Run against
~/Developer/amux right now:
- this checkout has DIVERGED (8 unpushed, 1109 behind origin/main)
it cannot fast-forward, so nothing here reaches origin until a human reconciles it
do NOT append to frustrations.md here — the write succeeds and reaches nobody
scripts/test-session-freshness.sh builds REAL git repos and runs the SHIPPED hook
against them. 10 assertions, verified to FAIL 3/10 against origin/main's hook.
Its control cases carry the weight: a current checkout must produce NO output,
and behind-only and ahead-only must NOT fire — a hook that warned unconditionally
would pass the incident case while being pure noise.
Wired into checks.yml, since a shell script outside crates/ is invisible to
rust.yml's path filter.
NOT FIXED, and it is the owner's call: the divergence itself (AMUX-49 / LR-1),
now 8 unpushed commits and 81 consecutive sync failures. This makes the
consequence visible; it does not reconcile the tree.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01B4vuEScunv4K6RMwQoT9xx
Amux-Session: amux-errors-and-bugs
…ailed CI The first cut relied on the ambient init.defaultBranch. Mine is 'main'; CI has none and falls back to 'master', so the bare repo's HEAD was master while the pushes created main, the clone tracked nothing, and the hook found no upstream and printed NOTHING. Four assertions failed and the output read as 'the hook is broken' when the harness was. Two changes: - pin init.defaultBranch=main inside the script and use Reinitialized existing Git repository in /Users/dorongreenspan/amux/.git/worktrees/wt-18/ / explicit , so the test depends on no ambient git config. - a HARNESS SELF-CHECK before the hook is consulted: assert the repo really is ahead=N behind=M as the case name claims, and report SETUP BROKEN as its own failure otherwise. A setup bug and a hook bug were indistinguishable, which is what cost the round-trip through CI. Verified under BOTH defaults: 10 passed, 0 failed with init.defaultBranch=main and again with =master (the CI condition, which previously reproduced the exact 6-passed/4-failed split). Amux-Session: amux-errors-and-bugs
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.
Four frustration entries were appended on 2026-08-17 to
~/Developer/amux— a checkout ~1000 commits behind, with unpushed local commits and an hourly sync job that had failed 80+ runs. That copy held 25 entries; origin held 124.The appends succeeded. No error, nothing to notice. They reached nobody.
.claude/rules/frustrations.mdsays to append to "frustrations.mdat the repo root", andCLAUDE.mdlives in that same stale checkout. So for a session working there — the documented working directory — the instruction is correct as written and produces an invisible write.Why this is its own defect, not just "the checkout is stale"
A stale checkout announces itself the moment you pull. A diverged one swallows writes instead of blocking a read. So the entire premise of the file is quietly lost: one frustration is a complaint and a cluster is an argument, and a cluster only forms in the file everyone reads.
Two halves, because either alone is weak
1. The rule now says "the repo root" means the checkout that can actually push, with the two
rev-listcommands to check it and the divergence condition spelled out.2. The SessionStart hook gains a third axis and says it at the one moment it matters. A doc that only asks you to remember is exactly what ethos rule 6 warns about; the hook is where a session already looks.
It fires only on divergence (ahead and behind), never on merely-behind — that's recoverable by a pull, and a push still reaches origin, so warning there would be noise. The hook's own header names silence-when-current as the reason it gets read at all.
Verified against the incident's own artifact
Run against
~/Developer/amuxright now:Test
scripts/test-session-freshness.shbuilds real git repos and runs the shipped hook against them. 10 assertions, verified to fail 3/10 againstorigin/main's hook.Its control cases carry the weight:
A hook that warned unconditionally would pass the incident case while being pure noise — and noise is how a banner stops being read.
Wired into
checks.yml, since a shell script outsidecrates/is invisible torust.yml's path filter.Not fixed here — owner's call
The divergence itself (AMUX-49 / LR-1): now 8 unpushed commits and 81 consecutive sync failures. This PR makes the consequence visible; it does not reconcile the tree, and reconciling a shared checkout is not an agent's decision.
🤖 Generated with Claude Code
https://claude.ai/code/session_01B4vuEScunv4K6RMwQoT9xx