Skip to content

fix(#322): the third reader of the same marker, and a freeze guard that outlived its claim - #329

Merged
fdaviddpt merged 1 commit into
mainfrom
fix/322-guards
Aug 8, 2026
Merged

fix(#322): the third reader of the same marker, and a freeze guard that outlived its claim#329
fdaviddpt merged 1 commit into
mainfrom
fix/322-guards

Conversation

@fdaviddpt

Copy link
Copy Markdown
Contributor

Builds on @jmossie82's #325 (merged). Their patch is unchanged — this is the sweep it opened up.

Part of #322

The third and fourth sites

10# landed in save-session.sh. scripts/post-tool-hook.sh reads the same tmp/last-save-ts at :335 and tmp/no-transcript-notice at :192, both case-guarded since #230, neither carrying 10#. So 08/09 clear the guard and are read as octal, the body is abandoned, and:

  • :335IN_COOLDOWN stays false, the hook forks a save inside the window
  • :192 — worse: the abandoned body swallows its own exit 0, so the line below the fi deletes the notice marker on the assumption a transcript was found

Both tests assert behaviour (save-session.pid absent / the notice marker surviving), not just the diagnostic. That mattered: the first draft asserted proc.stderr alone and passed against the broken code, because bash's line goes to hook-errors.log rather than stderr on this path.

SAVE_COOLDOWN was left alone, deliberately

I briefed this as having the same gap. It does not. LAST_TS is inside $(( )); SAVE_COOLDOWN is the right-hand operand of [ … -lt … ], the test builtin, which parses base 10 and does not evaluate:

$ [ 9 -lt 010 ] && echo decimal || echo octal      -> decimal
$ X='a[$(touch p3)]'; [ 5 -lt "$X" ]              -> integer expression expected, rc 2, nothing run

10# there would be inert and worse than inert — it would advertise a hazard at a site that has none, and its absence elsewhere would then read as a finding. The measurement is in the code comment so it does not get "fixed" later.

The judgment call that needs a second opinion

test_the_per_tool_call_path_is_not_touched (#298/#299) asserted post-tool-hook.sh is byte-identical to origin/main. That blocked a correct one-token fix and would block every future edit to the file. It is also self-cancelling: once the edit it objects to lands on main, it compares against the new main and passes. So what it actually asserts is "nobody has touched this file since main" — a different claim from the one its name makes, "the per-tool-call path is still cheap". A check that reads as evidence about the world when it is evidence about the diff.

Replaced the post-tool-hook.sh arm with the three properties #298/#299 care about — the divergence library is not sourced, case_divergence is not called, no git is spawned — each with its own message. Stronger than byte-equality after this merges, and cannot go vacuous. The lib-slug.sh / lib-memory-dir.sh arms keep their byte compare untouched.

If you would rather that were its own PR against #298, say so — but then the third site cannot land at all, because the guard forbids it.

Verification

Their tests against main's save-session.sh: 9 failed, 3 passed (the 3 are the two whitespace controls and the valid-marker case, by design). Their tests with their patch: 12 passed. My two, before my 10#: red with value too great for base at :325 and :186. Full suite 1519 passed, 43 skipped.

macOS + bash 3.2.57 only. bash 5 unverified — no bash 5 on this machine. CI is the authority.

…at outlived its claim

Co-Authored-By: Max <noreply>
@fdaviddpt

Copy link
Copy Markdown
Contributor Author

Code review

No blocking issues. One non-blocking finding, filed as #330 rather than held against this PR.

The byte-identity pin on the per-tool-call path is gone, and the three substring checks that replace it can be walked around — GIT=git; "$GIT" -C ..., an inlined divergence check without the literal case_divergence, or any added spawn or file read that is not a git call. #299 cited that byte-identity as its evidence for the zero-cost claim. Some loosening was unavoidable, since the old assertion blocked this PR's own one-character fix, so the right answer is a cost-counting pin rather than keeping the byte compare. Scored 60. Details in #330.

Checked and clean: the two 10# sites are both already case-guarded to digits, so 10# never meets an empty string; SAVE_COOLDOWN is correctly left alone as test's right operand; the 50-git-backup.sh / 50-git-restore.sh deferral matches #327's ordering; bash 3.2.57 compatibility holds across the changed lines; no comment or docstring left contradicting the code.

Adjacent, already open: #326 names post-tool-hook.sh:316-326 — the same line this PR touches — for a range check rather than a syntax check. A future-dated marker sticks the throttle on with no self-heal. Out of scope here, still open there.

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

@fdaviddpt
fdaviddpt merged commit 7d6f030 into main Aug 8, 2026
12 checks passed
@fdaviddpt
fdaviddpt deleted the fix/322-guards branch August 8, 2026 11:33
fdaviddpt added a commit that referenced this pull request Aug 8, 2026
…he hook reads (#331)

#327 asked for one 10# in 50-git-backup.sh. The sweep found six sinks across the
two git hooks: the backup cooldown marker, the restore hook's two fetch-state
reads, and all four consecutive-failure counters, whose failing increment
abandons the branch holding the ERROR log.

#327's severity claim does not hold: measured on bash 3.2.57 in the shape the
hook has, the failing arithmetic abandons the if body and the hook exits 0,
commits, and re-stamps -- misreports, not fails-to-preserve. Its docstring half
was already fixed by #329; that note is updated instead.

#324's premise that the test asserts nothing is also wrong -- the #261 legacy
carry-forward transports the dotted marker into the state dir before the
cooldown block. The test now writes through hook_state anyway, because the shim
is not the feature. What was genuinely missing is 08/09 and an assertion on
stderr.
fdaviddpt added a commit that referenced this pull request Aug 8, 2026
Bumps .claude-plugin/plugin.json to 0.18.0 and closes the Unreleased
section.

Also gives #329 its own entry. Its prose had been appended to #322's
bullet inside the already-released 0.17.0 section, so the file claimed
work that shipped after the 0.17.0 tag; those two paragraphs are moved
out and rewritten as a 0.18.0 entry, with the freeze-guard half of #329
documented for the first time.
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