Six octal reads, four of which delete the loudest error report in the file - #331
Merged
Conversation
…he hook reads #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. Co-Authored-By: Max <noreply>
This was referenced Aug 8, 2026
Closed
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.
Closes #324
Closes #327
Both issues were partly wrong. The code fixes are real and are wider than either described.
#324 — the headline claim does not reproduce
Filed as: the test writes the marker to a path the hook never reads, so the cooldown block is never entered and every corrupt value passes trivially.
Measured, both spellings behave identically:
The cause is the #261 legacy carry-forward at
50-git-backup.sh:138-147, which copies$REPO_ROOT/.last-git-backup-tsinto$GB_STATE_DIR/last-git-backup-tsbefore the cooldown block, and consumes the dotted file doing it. That is also why the reporter's glob showedlegacy dotted path exists? False— they read it as "the hook writes elsewhere and never saw mine", when it had moved theirs and read it.So the four existing parameters were exercising the guard. Three narrower things were wrong and are fixed:
hook_state()helper, resolved viagit rev-parse --git-common-dir.08/09were absentrc == 0and_slug_is_committedare both true with the octal error on this platform. The stderr assertion is the reporter's second point, and the only observable that separates broken from fixed on every bash.Their second point was right and is the one that mattered.
#327 — right about the sites, wrong about the severity
The issue claimed the hook dies and the git backup is stopped permanently, class
fails-to-preserve. Its repro is a flattened command list; the hook's actual shape isif [ -f "$COOLDOWN_MARKER" ]; then … fi, and on bash 3.2.57 that difference decides the outcome:The marker also self-heals —
state marker after: True 1786191071. So it is one skipped cooldown plus unexplained stderr, not a permanent stop.misreports, notfails-to-preserve. #329's own docstring already recorded this, measured on 3.2.57 and 5.2.37; the issue contradicted a note that was already correct.The sites, and there are seven
50-git-backup.shcooldown marker50-git-restore.sh_spawn_fetch50-git-restore.sh_fetch_healthrcbranch with_rcempty, so "abandoned" is reported as "FAILED (rc=unknown)" — a remedy offered for a failure that did not happengit-backup-rejectedcounterlog "ERROR: push REJECTED …"never runsgit-backup-commit-failedcounterlog "ERROR: commit FAILED …"never runsgit-backup-no-remotecountergit-restore-divergedcounterlog "ERROR: … DIVERGED …"never runsThe four counters were in neither issue and are the worse half. (Count corrected during review: seven sites, not six — the table below always had seven rows and the sentence above it said six. The two files also spell the guard differently, a 3-line
case/esacblock in50-git-backup.shand a single line in50-git-restore.sh, so each was verified independently rather than assumed from the first.) A corrupt counter does not mis-count — it deletes the loudest report in the file. That is #257 reached through the counter instead of throughexit 0, and it is invisible, because the arithmetic error goes to the detached worker's stderr rather than the hook's. The missing log line is the only detector.Bounded, which is what stops the sweep spreading:
[ "$x" -lt "$y" ]parses base 10 ([ 08 -lt 9 ]is true, no diagnostic).$(( ))is the only sink.The base was stale, and it changed both issues
mainin the shared clone was two commits behindorigin/main— missingeeb7a62(0.17.0) and7d6f030(#329). Rebased ontoorigin/mainrather than building on it. Consequences:10#was not "insave-session.shandlib-lock.shand nowhere else" — fix(#322): the third reader of the same marker, and a freeze guard that outlived its claim #329 also added it atpost-tool-hook.sh:192and:335. The inventory in 10# landed in save-session.sh only — 50-git-backup.sh still takes the octal path, where set -u makes it fatal, and a new test's docstring claims otherwise #327 was true when filed and is not true now.Without the rebase,
test_the_per_tool_call_path_is_not_touchedfails on any branch cut from that clone — it diffs the working tree againstorigin/main, so a stale localmainmakes it red for unrelated reasons. That would have been a phantom finding on this PR.Tests
RED, hooks stashed, on the rebased base:
GREEN:
8 passed in 10.52s. Full suite run:1523 passed, 43 skipped in 854.17s. Both touched test files carryskipif(sys.platform == "win32"), so the twelve-leg matrix is the only real Windows signal — unverified locally.Consolidation was considered and deliberately deferred
Seven more
case-guarded-then-$(( ))sites remain outside these two hooks:post-tool-hook.sh:302,session-start-hook.sh:875,save-session.sh:342,run-consolidation.sh:213,218,log.sh:1213,lib-lock.sh:191,doctor.sh:261.That is fifteen sites across four issues, each fixing a subset by hand — which is the mechanism that produced #327 in the first place. It is not done here because a shared
_remember_epoch_or_zerohelper belongs inlog.sh, and changing the sourcing contract for every hook in a repo that has taken ten Windows issues from seven reporters is the wrong risk to bundle into a two-issue PR. Filed separately, and the right shape is a lint rule as much as a helper —$(( ))reaching acase-guarded variable without10#is mechanically greppable, and ShellCheck does not flag it.