fix: checkpoint-marker hygiene F1-F5 — false stale-hooks warning, drift tracking, marker reapers, classifier staging cleanup#385
Merged
Conversation
em-recall-sessionstart.sh:62 gated the freshness diff on a top-level hooks/ dir, relocated by PR #373 — every SessionStart printed a false "source repo unavailable" warning. Probe the repo root only; the per-file loop owns file-level classification. Fixture updated to the real plugins/claude-code/hooks/ layout (tests 10-13 previously passed vacuously via the missing_source path); old code now fails 4 tests. Diagnosis: episode 20260611-234742-checkpoint-marker-confusion (F1). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…t drift (F3) The hook was installed + registered manually but never manifest-tracked, so the installed copy (6-file always-tier list, 2026-05-16 demotions) drifted ahead of the repo source unreported. Backport the installed runtime and add a HOOK_SPECS entry (SessionStart, timeout 5 — matches the existing manual registration) so install/freshness/cutover own it. Tests: copied + single registration + timeout + manifest row + ordering + reinstall idempotence; manifest count 12→13. Diagnosis: episode 20260611-234742-checkpoint-marker-confusion (F3). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
.preflight-done.<sid> + .last-user-prompt.<sid>.json were written every session by preflight-prompt-helper.sh with no cleanup path (51 of each accumulated since 2026-05-28). SessionEnd now reaps the own-session pair (primary dir only — no legacy forms ever existed); em-recall SessionStart sweeps crashed-session orphans older than 7 days (suffixed-only match, legacy suffix-less .preflight-done stays F7 scope; lstat, symlinks skipped). New marker-paths helpers keep both matchers RE-shared. 40 tests incl. 5-axis cwd matrix (nested/worktree/non-git/HOME/caller) per plan review R3 (episode 20260612-094844-…-32bf). Diagnosis: episode 20260611-234742-checkpoint-marker-confusion (F4). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…(F5)
The deny-hint flow stages pending-*.cmd in .checkpoints/classify/ but
--write never unlinked them (~115 accumulated). --write now consumes the
staging file on EVERY exit-0 status ('written' AND 'noop_same_tuple' —
single epilogue site) iff its realpath is contained in the validated
classifyDir and lstat says regular file; symlinks and out-of-dir files
are never touched. vacuumMarkers additionally reaps aged *.cmd as the
backstop for aborted flows (verdict *.json semantics unchanged).
Tests §CF5-§CF9 (consume, noop-consume, out-of-dir, symlink, vacuum).
Diagnosis: episode 20260611-234742-checkpoint-marker-confusion (F5).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codex code-review R1 P1 (episode 20260612-101323-…-a15e): realpath-first ordering let an in-dir symlink staging file delete its in-dir TARGET — a wrong-family deletion inside the correct root. lstat the argv path first; any symlink staging file is never consumed. Regression §CF10. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
lantiscooperdev
left a comment
Collaborator
There was a problem hiding this comment.
Substantive review (bot account, comment-only per Rule 17 — approval stays with the user).
Scope reviewed: diff main..fix/checkpoint-marker-hygiene (11 files, +736/−59, 5 commits), against diagnosis episode 20260611-234742-…-1c5f and the R3-accepted plan matrix.
Correctness
- F1 probe change is minimal and right-shaped: the repo-root existence check only guards the "repo gone" case; missing/relocated files fall through to the per-file loop's
missing_sourcebucket. The fixture rewrite closes a real vacuous-pass hole — pre-fix code now fails tests 10–13 with the exact production symptom. - F4 reapers bind to the documented authority roots (SessionEnd → stdin
.cwd→resolveRepoRoot; sweep → module-load repo root) and the suffixed-only matcher reuses the existing RE's capture group, so the two matchers can't drift. Legacy suffix-less.preflight-donecorrectly stays burn-in (F7) scope. - F5 consume-unlink: the R1 codex HOLD found a genuine wrong-family deletion (in-dir symlink → in-dir target consumed the target); fixed in
576ea43by lstat-ing the argv path before any canonicalization, with regression §CF10. Keying consume on the shared exit-0 epilogue meansnoop_same_tuplealso consumes — verified by §CF6.
Risk notes (non-blocking)
- The 7-day sweep cutoff means a >7-day-idle still-open session would get its preflight marker reaped and re-prompt once — fail-closed, accepted in plan review.
- Until the post-merge reinstall (F2), SessionStart will truthfully report
em-recall-sessionstart.sh(and after manifest regen,session-handoff-prompt.sh) as differing — expected, not a regression. vacuumMarkersnow counts.cmdfiles inscanned; any external consumer parsing vacuum counters sees larger numbers. None found in-repo.
Verification evidence: all four touched suites green locally and independently re-run by the codex reviewer (36/36, 40/40, 14/14, 87/87); adjacent marker suites green (40/25/56/25); live E2E reaped the 20+21 stale-marker backlog with fresh markers preserved.
Verdict: no blocking findings; merge-ready from this reviewer's perspective pending user approval.
…rs.mjs into plan-marker-validate (Rule 13) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
lantiscooperdev
approved these changes
Jun 12, 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.
Summary
Checkpoint-marker hygiene F1–F5 per diagnosis episode
20260611-234742-checkpoint-marker-confusion-diagnosis-20-1c5f(workplan v119 item 2). Kills the every-session false "hooks may be stale" warning and stops the unbounded marker accumulation in.checkpoints/.08f8f76) —em-recall-sessionstart.sh:62probed the pre-PR-feat(rfc-008): R0c/P1a — git mv hooks/ → plugins/claude-code/hooks/ #373hooks/layout; every SessionStart printed a false "source repo unavailable". Probe the repo root only; the per-file manifest loop owns file-level classification. Test fixture moved to the realplugins/claude-code/hooks/layout (tests 10–13 previously passed vacuously through themissing_sourcepath); old code now fails 4 tests.c762b6a) —session-handoff-prompt.shwas installed + registered but never manifest-tracked; the installed copy drifted ahead unreported. Backported the installed runtime (6-file always-tier list) and added aHOOK_SPECSentry (SessionStart, timeout 5) so install/freshness/cutover own it.20905d9, closes the FU(279): validator parity + SessionStart/End wiring + canonicalize asymmetry + --root= form #283 lifecycle halves) —.preflight-done.<sid>+.last-user-prompt.<sid>.jsonhad no cleanup path (51 of each accumulated). SessionEnd reaps the own-session pair; em-recall SessionStart sweeps crashed-session orphans older than 7 days (suffixed-only match — legacy suffix-less.preflight-donestays burn-in/F7 scope; lstat, symlinks skipped).d53259c+576ea43) —--writeconsumes the one-shotpending-*.cmdstaging file on every exit-0 status (written+noop_same_tuple) iff lstat says regular file and realpath containment in the validated classify dir holds; vacuum additionally reaps aged*.cmd.Reviews (Rule 18)
…093852-3bdb→…094442-2ddd→…094844-32bf); R2 corrected the linked-worktree/nested-cwd authority-root matrix.…101323-a15e— reproduced in-dir-symlink wrong-family deletion in consume-unlink) → fixed in576ea43(lstat argv path before realpath) + regression §CF10 → R2 ACCEPT (…101922-ddda).Tests
tests/test-em-recall-sessionstart.sh14/14 (negative-verified: pre-fix code fails 10–13 with the live symptom)tests/test-install-hooks.sh87/87tests/test-preflight-marker-reapers.mjs40/40 — new; 5-axis cwd matrix (nested cwd, linked worktree, non-git cwd, fake HOME, caller-cwd separation) with on-disk canary assertions per branchtests/test-classifier-marker.mjs36/36 (§CF5–§CF10 new)E2E (live repo)
--session-startrun: backlog sweep reaped 20 + 21 stale per-session markers (50→30 / 51→30), fresh + own-session preserved..checkpoints/is markers-only again.Post-merge
node install.mjs --tool claude-code --install-hooks(+--install-hooks-forcefor the two drifted files), thennode tools/migration-cutover.mjs→ expect all-OK (was OK=96 DIFF=2 MISSING=5).🤖 Generated with Claude Code