Skip to content

fix: checkpoint-marker hygiene F1-F5 — false stale-hooks warning, drift tracking, marker reapers, classifier staging cleanup#385

Merged
lantiscooperdev merged 6 commits into
mainfrom
fix/checkpoint-marker-hygiene
Jun 12, 2026
Merged

fix: checkpoint-marker hygiene F1-F5 — false stale-hooks warning, drift tracking, marker reapers, classifier staging cleanup#385
lantiscooperdev merged 6 commits into
mainfrom
fix/checkpoint-marker-hygiene

Conversation

@lantisprime

Copy link
Copy Markdown
Owner

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

  • F1 (08f8f76) — em-recall-sessionstart.sh:62 probed the pre-PR-feat(rfc-008): R0c/P1a — git mv hooks/ → plugins/claude-code/hooks/ #373 hooks/ 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 real plugins/claude-code/hooks/ layout (tests 10–13 previously passed vacuously through the missing_source path); old code now fails 4 tests.
  • F3 (c762b6a) — session-handoff-prompt.sh was installed + registered but never manifest-tracked; the installed copy drifted ahead unreported. Backported the installed runtime (6-file always-tier list) and added a HOOK_SPECS entry (SessionStart, timeout 5) so install/freshness/cutover own it.
  • F4 (20905d9, closes the FU(279): validator parity + SessionStart/End wiring + canonicalize asymmetry + --root= form #283 lifecycle halves) — .preflight-done.<sid> + .last-user-prompt.<sid>.json had 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-done stays burn-in/F7 scope; lstat, symlinks skipped).
  • F5 (d53259c + 576ea43) — --write consumes the one-shot pending-*.cmd staging 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)

  • Plan: codex 3 rounds → ACCEPT (…093852-3bdb…094442-2ddd…094844-32bf); R2 corrected the linked-worktree/nested-cwd authority-root matrix.
  • Code: codex R1 HOLD (…101323-a15e — reproduced in-dir-symlink wrong-family deletion in consume-unlink) → fixed in 576ea43 (lstat argv path before realpath) + regression §CF10 → R2 ACCEPT (…101922-ddda).

Tests

  • tests/test-em-recall-sessionstart.sh 14/14 (negative-verified: pre-fix code fails 10–13 with the live symptom)
  • tests/test-install-hooks.sh 87/87
  • tests/test-preflight-marker-reapers.mjs 40/40 — new; 5-axis cwd matrix (nested cwd, linked worktree, non-git cwd, fake HOME, caller-cwd separation) with on-disk canary assertions per branch
  • tests/test-classifier-marker.mjs 36/36 (§CF5–§CF10 new)
  • Adjacent regressions green: quartet-cleanup 40/40, plan-marker-sweep 25/25, baseline-monotonic 56/56, marker-paths 25/25

E2E (live repo)

  • Fixed hook vs live manifest: false warning gone; truthful drift report for the one genuinely-stale tracked file.
  • Live --session-start run: backlog sweep reaped 20 + 21 stale per-session markers (50→30 / 51→30), fresh + own-session preserved.
  • F6 scratch sweep done locally: .checkpoints/ is markers-only again.

Post-merge

  • F2: node install.mjs --tool claude-code --install-hooks (+ --install-hooks-force for the two drifted files), then node tools/migration-cutover.mjs → expect all-OK (was OK=96 DIFF=2 MISSING=5).
  • F7 (legacy burn-in cutover, gated on cutover green 7d) → tracked in a follow-up issue.

🤖 Generated with Claude Code

lantisprime and others added 5 commits June 12, 2026 17:53
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 lantiscooperdev left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_source bucket. 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 .cwdresolveRepoRoot; 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-done correctly 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 576ea43 by lstat-ing the argv path before any canonicalization, with regression §CF10. Keying consume on the shared exit-0 epilogue means noop_same_tuple also consumes — verified by §CF6.

Risk notes (non-blocking)

  1. 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.
  2. 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.
  3. vacuumMarkers now counts .cmd files in scanned; 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 lantiscooperdev merged commit 4a892a4 into main Jun 12, 2026
2 checks passed
@lantisprime lantisprime deleted the fix/checkpoint-marker-hygiene branch June 12, 2026 10:31
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.

2 participants