Bound the marker's range, not just its syntax — and let a check do the sweep - #336
Merged
Conversation
…et a check do the sweep #326 — the four cooldown guards validate SYNTAX and not RANGE. A digits-only marker ahead of now is accepted, makes ELAPSED negative, and a negative ELAPSED is -lt any cooldown, so the gate takes its `exit 0` — which sits ABOVE the line that rewrites the marker. The self-heal the last three releases rest on is unreachable on exactly the path that needs it: no save, no compression, no git backup, permanently and mutely, until the wall clock catches up. Three states, not two. Out of range now PROCEEDS, resets the marker at the point of rejection where the reset is reachable, and says so once in hook-errors.log via a new report_error() in log.sh. A silent clamp would trade a mute stuck throttle for a mute wrong value. post-tool-hook.sh is deliberately asymmetrical: it declines the cooldown it cannot substantiate and does nothing else — no rewrite (the marker is save-session.sh's), no diagnostic (one per tool call), no added spawn or read (#299/#330). The source comment at save-session.sh:155 asserted the opposite and is corrected here. That comment, not the CHANGELOG, is why the issue was reopened: PR #328 closed it by referencing it. #332 — twelve remaining sites read a case-guarded value into $(( )) with no 10#, not the seven the issue lists. The five extra were found by tests/test_arith_base_lint_332.py, which sweeps scripts/ and hooks.d/ for the shape mechanically and is the part that prevents a sixth issue. ShellCheck does not flag it (koalaman/shellcheck#2679). No shared helper: a helper a new author does not know about cannot stop them writing the sixteenth site. tests/test_ndc_day_boundary.py was suppressing NDC by writing a marker 34,000 years in the future — #326's defect used as an off switch. Switched to _suppress_ndc, the brake the product documents (#159). Targeted RED verified before the fix at all four sites; full suite 1545 passed, 43 skipped. Windows unverified — CI is the authority. Co-Authored-By: Max <noreply>
…ld have cried wolf Findings from the /code-review pass on PR #336, fixed in the same run. FIFTH SITE (#326). hooks.d/before_session_start/50-git-restore.sh reads `started=` through the same digits-only case. A value ahead of now makes _age negative, so _fetch_health answers "in-flight" — the one state meaning "wait, something is already running" — about nothing, and _spawn_fetch takes its early return so the only writers of the record never run. Same geometry as the four sites #326 enumerates, and not on its list. The harm is worse than a wrong label: the caller then prints "already up to date with origin/main" off refs no fetch refreshed, which is exactly what _fetch_health's own header forbids. Now answers "abandoned" and spawns a real fetch that rewrites the record. _fetch_health stays pure — its stdout is the verdict, read through $( ) — so the diagnostic goes in _spawn_fetch. LINT COULD NOT BE TRUSTED AS A GATE (#332). `case "$1" in` captures the guarded name as the bare string "1", and the matcher did not require the $ sigil, so every `$(( x + 1 ))` in a file guarding a positional parameter was a finding. lib-lock.sh guards two, so the next ordinary arithmetic there would have failed CI for no reason. Numeric names now require the sigil. ITS OWN FIXTURE WAS NOT THE SHAPE IT CLAIMED. `''` inside a single-quoted Python literal closes it and opens another, so the planted guard had silently become `case "$LAST" in |*[!0-9]*)`. A self-test whose fixture is not what it plants still goes green, and green here is supposed to mean "looked, found none". Pinned by a test. STALE LINE-NUMBER CITATIONS. Six comments this PR added cited line numbers; three were already wrong at commit time. All replaced with construct names. A PR that exists because a comment outlived the truth should not add more. Targeted RED verified before each fix — including catching that the first draft of the restore tests passed vacuously, because a doubled backslash put a literal \\n in the state file and the digits-only guard rejected it for the wrong reason. Full suite 1551 passed, 43 skipped. Windows unverified. Co-Authored-By: Max <noreply>
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 #326
Closes #332
#326 — a digits-only marker ahead of now stuck the throttle on, permanently and mutely
case "$LAST_MOD" in ''|*[!0-9]*) LAST_MOD=0 ;; esacrejects the syntax error and does not bound the value. A marker holding a future epoch — clock stepped back by NTP, a VM snapshot restore, a container clock jump, or an overflowing integer — makesELAPSEDnegative, so[ "$ELAPSED" -lt "$SAVE_COOLDOWN" ]is true andexit 0fires. Thatexit 0sits above the line that rewrites the marker, so the self-heal the whole design rests on is unreachable on exactly the path that needs it.Every subsequent run skips. Nothing on stderr, nothing in
hook-errors.log. The user discovers it by noticing that nothing has been saved for a week.Four sites, each now rejecting a negative
ELAPSED, resetting the marker at the point of rejection rather than below the exit, and saying so:save-session.sh:174save-session.sh:627now.mdgrows unbounded — and here the existing rewrite is insideif RUN_NDC, i.e. inside the branch the bug skips, so the immediate reset is load-bearing rather than belt-and-braces50-git-backup.sh:180post-tool-hook.sh:355The misleading comment at
save-session.sh:155-157— which asserted the marker self-heals and one corruption costs one skipped cooldown — is corrected. It was still there after v0.17.0 corrected only the CHANGELOG.Proceed rather than abort. Epoch seconds do not move for DST or a timezone change, so the "laptop suspended over a boundary" case does not arise; only a real clock step produces this. Proceeding costs one extra save, refusing costs the preservation.
The diagnostic goes to
hook-errors.log, because/remember:doctorreports "Recent errors" from that file and it is what maintainers ask reporters to paste. Not emitted frompost-tool-hook.sh: one line per tool call for as long as the clock is behind is noise, andsave-session.shemits exactly one when it heals. That site takes a pure range test — no rewrite, no diagnostic, zero added spawns or reads.#332 — the hand-fixing was the defect, so a check does the sweep
The issue says seven sites remain. There are twelve, and the five it does not name were found by the check rather than by reading — which is the argument for having one:
tests/test_arith_base_lint_332.pyfails on any value guarded by a digits-onlycasethat reaches$(( ))without10#. All twelve sites fixed — radix after the case, never instead of it.No shared helper, and the issue is right about why. Not for the sourcing-cost reason: all four #326 sites already source
log.sh, soreport_erroradds a function definition and nothing else — no spawn, no read, invisible totest_post_tool_hook_spawns.pyandtest_case_divergence_298.py, both green. The real reason is that a helper cannot stop an author who has never heard of it. The check can.The suite was using #326 as an off switch
tests/test_ndc_day_boundary.pysuppressed NDC at three sites by writing2**40intotmp/last-ndc.ts— a marker ~34,000 years ahead — relying on the negativeELAPSEDto engage the cooldown. It went red the moment the guard landed. Switched to_suppress_ndc(), the brake #159 established as supported.A targeted-only run would have shipped that red. It is the reason the full suite ran.
Tests
TDD, red first: 10 targeted failures before implementation, plus the lint red listing all twelve sites. The 6 initially-passing tests are deliberate controls — a fresh marker still throttles at each gate, and the overflow parameters really do go negative against unfixed code.
Green: 16 + 6 targeted. Full suite: 1545 passed, 43 skipped in 892.37s. Windows unverified; the only new test that runs there is the pure-Python lint.
Out of scope
50-git-backup.sh:167still says "Falling back to 0 self-heals" without the range caveat the twosave-session.shcomments now carry.post-tool-hook.shgained 18 comment lines and a variable here without any of them noticing — exactly the gap that issue describes.