Skip to content

fix(rules): warn when a diverged checkout will strand a frustrations entry - #118

Merged
Dygreens merged 2 commits into
mainfrom
fix/frustrations-stranded-checkout
Aug 18, 2026
Merged

fix(rules): warn when a diverged checkout will strand a frustrations entry#118
Dygreens merged 2 commits into
mainfrom
fix/frustrations-stranded-checkout

Conversation

@Dygreens

Copy link
Copy Markdown
Collaborator

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.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.

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-list commands 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/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
    log friction in a clone that is current instead (.claude/rules/frustrations.md)

Test

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:

case expected
current checkout no output at all
diverged (2 unpushed, 3 behind) warns, names both counts
behind only must not fire
ahead only must not fire

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 outside crates/ is invisible to rust.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

…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
@Dygreens
Dygreens merged commit 14acb6b into main Aug 18, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant