perf(context-guard): skip state writes that would not change the marker - #3736
Conversation
Persist the /planning:interview contract for the prompt-hooks review at docs/topics/prompt-hooks-review/PLAN.md, the contract tier per docs/conventions/topic-docs (committed on the task branch only, pruned before merge). Seven register rows, all answered. Scope locks to the two files this lane owns: plugins/context-guard/hooks/hooks.json and zone-crossing-inject.sh. Everything larger the fleet audit found is recorded as out-of-scope with its receiving lane named. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ECoW7mi7Zod61utjdyWtRE
zone-crossing-inject.sh fires once per UserPromptSubmit and once per PostToolBatch, so a three-batch turn in one zone rewrote both state markers four times to the values they already held. Each marker is now written only when its raw on-disk bytes differ from the new value. The raw read, before normalization, is the comparand, so a legacy-format marker is still rewritten in the current format and an absent marker still latches on the first fire. Write ordering, the fail-open posture and the .zone rollback on a failed .armed write are unchanged; the rollback now fires only when this call was the one that moved .zone. hooks.json: all four rows drop timeout from 60 to 10 (the hooks reference lowers the UserPromptSubmit command default to 30; the scripts measure at roughly 30 ms), and the file gains its documented top-level description. The Brief gains a measured-baseline table from the fleet audit, holding only the timings peer review left standing. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ECoW7mi7Zod61utjdyWtRE
…eview-cf00b1 # Conflicts: # plugins/context-guard/CHANGELOG.md
docs/topics/<slug>/ is contract tier per the topic-docs convention: committed on the task branch, pruned before merge, with durable outcomes graduated first. The Brief's substance is the PR body; the measured baseline table's numbers are routed to #3685 as evidence. The contract-slice-diff hygiene gate was the one failing sub-gate of 56 on the lint lane. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ECoW7mi7Zod61utjdyWtRE
…eview-cf00b1 # Conflicts: # plugins/context-guard/CHANGELOG.md
|
Babysit lane claim pinned to head Observed at claim time: draft, Scope of this claim is conflict resolution only. Draft promotion is a separate decision and is not part of this intervention. Claim expires at the timestamp above. Generated by Claude Code |
…eview-cf00b1 Resolves the context-guard CHANGELOG conflict. Both sides independently claimed 0.7.41: main's #3732 shipped the vendored hook-utils.sh buffer_stdin/repo_root change under that version, and this branch's 027d11a claimed it for the marker-write skip. Main's 0.7.41 is already released, so it keeps the version it shipped under and this branch's entry moves up to 0.7.42, with plugin.json bumped to match. Both entries survive, per the branch's own stated convention that each merge places this entry above the newer one under its own version. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VPLatLkg4329L8eyfxhuMa
Merge conflict resolved — still draft, promotion deferredLane What was conflictingOne file:
How it was resolvedmain's 0.7.41 is already released, so it keeps the version it shipped under. This branch's entry moves up to 0.7.42, and Merge commit Verification on the resolved head
Why this is still a draftConflict resolution and draft promotion are separate decisions, and two things block a confident promotion:
Nothing in the diff itself looks incomplete — no WIP markers, and the verification section is thorough. The hold is the stale body plus the absent review evidence, not the code. Lane claim released. Generated by Claude Code |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
Claude finished @kyle-sexton's task in 3m 48s —— View job Claude finished reviewing this PR
Review summaryScope: I traced the write-skip logic in
One non-blocking observation, not tied to a changed line so it's here rather than inline: the write-skip is the entire point of this PR, but no test asserts the write is actually skipped (e.g., by making the state dir read-only after seeding a marker with the current value and confirming the hook still exits silently with no error — which would fail under the pre-PR code that unconditionally attempts the write). The existing 58 tests all assert externally-observable behavior (silence/injection), which passes identically whether or not the skip fires, so a future regression that quietly reverted to always-writing would go undetected. See the skip-write block itself: claude-code-plugins/plugins/context-guard/hooks/zone-crossing-inject.sh Lines 301 to 325 in 5acee10 Nothing here rises to a blocking finding. No inline comments posted. |
|
Claude finished @kyle-sexton's task in 1m 5s —— View job Security review complete ✅
No security issues found. This PR is a pure performance/metadata change scoped to
No injection surfaces, no authorization/access-control changes, no secrets/token handling, no GitHub Actions workflow changes, and no permission widening in this diff. Per the skip gate in |
|
Last security-reviewed head: |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5acee10636
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
The draft dropped every hooks.json timeout from 60 to 10 on the strength of the hooks reference's 30-second UserPromptSubmit default and a 30 ms quiet-host measurement. That reintroduced the defect the 0.4.8 entry fixed: on Windows 11 / Git Bash with Defender real-time protection, zone-crossing-inject.sh measured 22.0 s, so a 10-second cap cancels the advisory on essentially every fire while still charging the full wall time. The README sizes the cap from that measurement and holds it at 60 until the profile is re-measured. Nothing in this branch re-measured it. Flagged by the Codex review on 5acee10. The state-write skip and the top-level description are unchanged. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ECoW7mi7Zod61utjdyWtRE
|
Claude has reviewed this PR 1 time. The lane skips further automatic reviews after 5; deleting this comment resets the count. |
The review lane observed that every assertion in the suite reads stdout, which is identical whether the state markers were rewritten or skipped, so a revert to unconditional writes would pass all 58. Case 12c backdates both markers to 2000-01-01 and a reference file to 2000-01-02, fires the hook on an unchanged zone, and asserts neither marker is newer than the reference; a positive control confirms a mismatched marker is still rewritten. bash -nt keeps it free of any stat(1) dialect, and mtimes are not subject to root's permission bypass, which is why it is not a chmod test. Against the previous hook exactly the two skip assertions fail. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ECoW7mi7Zod61utjdyWtRE
No related issue: the defect was found by an in-session fleet audit that had no tracking issue; the audit's findings on other lanes are tracked in #3685, #3721 and #3727.
Summary
zone-crossing-inject.shfires once perUserPromptSubmitand once perPostToolBatch, so a three-batch turn that stays in one zone fired four times and rewrote both state markers four times to the values they already held. The armed-rank gate already suppressed the extra notices, so this was wasted I/O rather than duplicate injection. Alongside it,hooks.jsonlacked its documented top-leveldescription.Fix
.zonerollback on a failed.armedwrite are unchanged; the rollback now fires only when this call was the one that moved.zone.bash -ntkeeps it free of anystat(1)dialect, and mtimes are not subject to root's permission bypass, which is why it is not a chmod test.hooks.jsongains a top-leveldescription.context-guardbumps to 0.7.42 with a CHANGELOG entry. Main shipped 0.7.39 in feat(session-flow): shape-2 handoff save-points with the resume prompt on screen and in the file #3718, 0.7.40 in fix(hooks): PreToolUse guard remediation — scratch-root default, kill-switch hoist, MCP coverage, two conventions #3727 and 0.7.41 in perf(hook-utils): drop buffer_stdin startup subshells, repo_root tr, and emit_tel jq #3732 while this branch was open; each merge placed this entry above the newer one under its own version, so consumers on any of them still receive this change.Withdrawn in review. The draft also dropped the four
hooks.jsontimeouts from 60 to 10, reasoning from the hooks reference's 30-secondUserPromptSubmitdefault and a 30 ms quiet-host measurement. The Codex review on5acee106pointed at the 0.4.8 CHANGELOG entry and the README paragraph that own that value: on Windows 11 / Git Bash with Defender real-time protection,zone-crossing-inject.shmeasured 22.0 s, and a 10-second cap was the exact defect 0.4.8 fixed, cancelling the advisory on essentially every fire while still charging the full wall time. Nothing on this branch re-measured that profile, so3db2c457restores all four rows to 60 and the CHANGELOG entry says why they stay.Five files:
zone-crossing-inject.sh,zone-crossing-inject.test.sh,hooks.json,plugin.json,CHANGELOG.md, all underplugins/context-guard/. The interview Brief that scoped this lived indocs/topics/prompt-hooks-review/on the branch and is pruned before merge per the topic-docs convention; its substance is this body, and the audit's measured baseline is routed to #3685.Verification
shellcheckclean on the changed script and the test, plain and with the repo.shellcheckrc;check-shell-portability.sh origin/mainclean on both.plugins/context-guard/hooks/zone-crossing-inject.test.sh: 58 pass, 0 fail at every head through3db2c457; 63 pass, 0 fail on860d66d6with case 12c. Againstorigin/main's hook the suite reads 61 pass, 2 fail, the two failures being exactly the skip assertions. The first attempt at the fix failed one case (0.7.1-format marker not normalized): comparing against the normalized read skipped the rewrite of a legacy marker. Fixed by comparing against the raw read.scripts/affected-tests.sh --run: all selected suites pass at every head (26 before the first merge, 27 onf7cb502f, 2 on the pruned head511b549fonce the Brief stopped driving reference-based selection, 2 onfcc3f8cd, all selected on5acee106, 2 on3db2c457, 3 on860d66d6), zeroFAILlines. A mid-merge run had reported three failures in guardrails, session-flow and source-control; on a cleanorigin/mainworktree the guardrails suite passes 213/0 and the session-flow failure reproduces identically, and this branch touches none of those plugins.fcc3f8cd, after merging the fix(hooks): PreToolUse guard remediation — scratch-root default, kill-switch hoist, MCP coverage, two conventions #3727 squash: the three context-guard suites pass 18/58/25, and main's newscripts/check-killswitch-hoist.shreports 15 of 15 guards clean.5acee106, after merging the perf(hook-utils): drop buffer_stdin startup subshells, repo_root tr, and emit_tel jq #3732 squash (resolved by the babysit lane, see its comment on this PR): the three context-guard suites pass 18/58/25 again,scripts/sync-hook-utils.sh --checkreports all 17 copies matching, and the diff against main is still exactly the files above.3db2c457: the three context-guard suites pass 18/58/25, markdownlint clean on the CHANGELOG,hooks.jsonparses and everytimeoutreads 60 again.scripts/check-changelog-parity.sh--check-bump origin/main,--check-order,--check-preserved origin/main: all exit 0 on every head.scripts/check-contract-slice-prune.sh --check-diff origin/main: passes on511b549f. This was the one failing sub-gate of 56 on the lint lane atf7cb502f(contract-slice-diff); every other hygiene sub-gate was already green there.5acee106: security-review reported no findings; the Claude review lane found no blocking issue and raised the missing skip test, addressed in860d66d6; the Codex review raised the timeout finding above, addressed in3db2c457.Related
block-hook-bypasscorpus sweep (PreToolUse lane).scripts/check-killswitch-hoist.sh. Landed first and took context-guard 0.7.40.hook-utils.shstartup-subshell trim. Landed second and took context-guard 0.7.41, so this PR's entry sits above it as 0.7.42..work/prompt-hooks-review/interview-checklist.md(untracked, memory tier).🤖 Generated with Claude Code
https://claude.ai/code/session_01ECoW7mi7Zod61utjdyWtRE