feat(context-guard): zone-crossing hooks, token bands, and the workflow continuation router - #1603
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 40aa5b69d6
ℹ️ 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".
…ow continuation router (#1475, #1476) context-guard 0.4.0 — the plugin's first shipped consumer of its own seam: - hooks/hooks.json registers four fail-open handlers with co-located contract tests: zone-crossing-inject.sh (PostToolBatch + UserPromptSubmit; injects continuation guidance once per transition into a worse zone, silent on unchanged/improving/unknown), zone-gate.sh (PreToolUse over Write|Edit|NotebookEdit|Agent|Workflow; blocking mode only, fresh dumb-zone snapshots past a grace budget, handoff-path writes exempt so a handoff is always writable), post-compact-mark.sh (PostCompact, side-effect-only; persists the evidence-degraded marker and re-arms the gate's grace budget). - context-zone.sh resolves two shapes and combines them conservatively: percentage bands over used_percentage and window-class token bands over occupancy (total_input_tokens + total_output_tokens; 200k class 100000/160000, 1M class 200000/400000), with a plausibility guard for the pre-2.1.132 cumulative token-field semantics. zones.json gains an optional token_bands object validated per shape; v1 percentage-only files keep working unchanged. Reader contract documents the occupancy definition, verbatim combination rule, version floor, marker, hook surface, band provenance, and the percentage-key retirement trigger. - Config per docs/conventions/hook-config-delivery channel B with in-script defaults; three new userConfig options; telemetry producers registered with data schemas. plugin-quality 0.2.0 — first external inliner migrated: the context-gate understands the v2 band shape (no more token-shape split-brain), inlines the combination rule verbatim, honors the evidence-degraded marker, and gains scripts/zones-inline-drift.test.sh — the consumer-lane drift check the reader contract's inline-floor rule names, running in the plugin-gate CI job. session-flow 0.17.14 — workflow gains the end-of-phase continuation router (context/continuation.md spoke + `continue` argument mode): outcome set derived from the mechanisms the plugin actually installs plus built-ins, every ordering edge with a stated purpose, zone input presence-gated on the context-guard reader contract with no inlined band values, and the handoff-relay convention for workers (worker writes its own handoff, returns the path only; parent spawns a successor without reading it). Verification: context-zone.test.sh 64/64; zone-crossing-inject.test.sh 11/11; zone-gate.test.sh 13/13; post-compact-mark.test.sh 11/11; zones-inline-drift.test.sh 9/9; shellcheck clean; silent-skip, portability, changelog-parity, hook-utils-sync, markdownlint, typos, and check-changed-skills gates all green locally. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qLeNKmcBFxZgsfEksLomX
Matches the stored mode of every sibling plugin's hooks/*.sh and *.test.sh (100755); Windows working trees do not propagate the bit automatically. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qLeNKmcBFxZgsfEksLomX
Generated block (scripts/generate-catalog.mjs); plugin.json owns the text. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qLeNKmcBFxZgsfEksLomX
…ility scanning A plugin's hooks/hook-utils.sh is a registered sync copy (scripts/cross-plugin-source-registry.txt) held byte-identical to lib/hook-utils.sh by its own drift gate, so a copy can never carry a local fix or a portability-ok annotation — scanning it flags content no copy edit is allowed to change. The source stays scannable, so a lib change is still gated exactly once, at the pressure point where a fix propagates to every copy. Surfaced by the first new hook-utils.sh copy added since the GNU-regex-escape tokens went active: line 1114's bash glob class [\<\>] is matched by the \< and \> tokens even though it is not a GNU regex escape. Repro-first per docs/conventions/hook-precision: the new stay-quiet suite case (sandboxed script copy + one-line registry + --all over a synthetic tree) fails against the unmodified checker and passes with the exemption; an unregistered sibling file in the same tree still flags. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qLeNKmcBFxZgsfEksLomX
…marker consumed, prune markers Applies the pre-PR /code-review findings, each reproduced by a failing test first: - Large payloads: all three hooks now read stdin through a plugin-local chunked drain loop (hooks/payload.sh, the tee's proven read -N pattern). The shared lib's single bounded read times out on Windows/MSYS pipes at roughly 80KB — exactly the payloads these events carry (PostCompact's full compact_summary, a big Write's tool_input, PostToolBatch's serialized results) — which silently suppressed the marker and failed the blocking gate open for precisely the biggest writes. Per-hook 130-150KB regression tests fail against the single-read form. - Marker consumption: both zone consumers now treat a session with the .compacted marker as effectively dumb regardless of the resolved word (including a green post-compaction reading and unknown), implementing the reader contract's "evidence-degraded regardless of zone" rule so the marker is never write-only and compaction cannot disarm the gate whose own continuation-router fallthrough recommends /compact. The PostCompact grace-counter reset stays: a fresh budget, not a disarmed gate. - Marker growth: post-compact-mark.sh prunes sibling *.compacted files on the tee's 14-day cutoff (its sweep matches *.json only). Reader contract, README, and CHANGELOG updated to match. Suites: 13/15/14 plus the drift lane 9/9; shellcheck, silent-skip, portability, markdownlint, typos green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qLeNKmcBFxZgsfEksLomX
40aa5b6 to
0ae26b3
Compare
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0ae26b3b16
ℹ️ 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".
… a data-string SC2088 - Drop issue back-references from script comments (version control owns history; the reader contract carries the provenance pointer). - shellcheck disable=SC2088 on the drift lane with the justification at the site: its tilde strings are documented contract phrases grep-matched as data, never paths the script expands. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qLeNKmcBFxZgsfEksLomX
|
Claude encountered an error after 1s —— View job I'll analyze this and get back to you. |
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: dc91f81f43
ℹ️ 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".
…dget base 10
Two independent blocking-gate defects, each with a regression test that fails
against the previous form.
Grace counter (concurrency). Claude starts matched tools in parallel, so
several PreToolUse hook processes run at once against one session's counter.
The read-modify-write let them all read the same count and record the same
increment, so far more than the configured budget was allowed — measured 6-7
allowed of 24 concurrent calls against a budget of 4. Each call now appends
one byte and takes the file size as its count: single-byte O_APPEND writes do
not interleave, so the calls occupy distinct positions and the call landing at
position k always reads a size >= k, which caps the allowed set at GRACE. The
only residual error is over-denial, the conservative direction for a gate.
Grace budget (base). A digit-only value with a leading zero (08) cleared the
validation but is an octal literal in Bash arithmetic: ((count <= GRACE))
errored on the invalid digit, evaluated false, and denied the FIRST call
instead of allowing eight. It was also an invalid JSON number in the telemetry
payload ({"grace":08}). The value is now length-bounded and normalized to
canonical decimal once, so the arithmetic, the operator-facing deny reason,
and the telemetry all carry the same number.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GSg6k5yejGqUkCGVvxaKTF
…t occupancy alone The occupancy > context_window_size guard cannot rule out pre-2.1.132 cumulative-totals semantics, because a cumulative value BELOW the window size is indistinguishable from a real occupancy: 170k cumulative in a 200k window sits inside the window, passes the guard, and resolves dumb while the live context may be smart-zone. Numbers alone can never settle it, so the token shape now requires an explicit version signal. - statusline-tee.sh tees cli_version from the statusline payload's top-level `version` field (the Claude Code version), copied only when it is a string and never fabricated. - context-zone.sh makes the token shape computable only when cli_version is present, purely numeric dotted, and >= 2.1.132. Absent, malformed, or older leaves the percentage shape standing alone. Compared component-wise in awk, not sort -V, which the portability lane rejects. The occupancy > window guard stays as an independent check for corrupt or forged data — snapshots carry no writer authentication, so cli_version is untrusted like every other field. Also reconciles the reader contract's capability table with its own combination rule. The table mandated `unknown` for a null/missing/non-numeric used_percentage while the rule two sections below said a single computable shape stands alone, so consumers following different sections resolved different zones for one snapshot. The table now classifies per shape and marks only the snapshot-wide observations as `unknown`; the resolver already behaved this way, so the doc was the wrong side. plugin-quality's inlined consumer floor and its cross-plugin drift lane pick up the version floor. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GSg6k5yejGqUkCGVvxaKTF
…onflated hook status Continuation router. Question 3 asked whether the work passes to another agent, which a background continuation always does, so the first-yes rule selected handoff and question 4's continue-in-background outcome was unreachable. The background question now comes first: it is the strictly narrower gate on the same save-point engine (explicit request AND autonomy), and a background request that still needs human input falls through to the handoff question rather than to /compact. The outcome-set paragraph's "reached from question 4" pointer follows the renumber. Setup check. `context_guard_hooks_enabled: false` leaves the plugin enabled and the hooks registered while every hook exits immediately, so equating plugin-enablement with active hooks reported the opposite of the runtime state exactly when an operator was diagnosing missing injections or gating. The step now reports registration, hook-set activation (read from the configured kill-switch value), and gate posture (zone_hook_mode) as three separate facts, and answers UNKNOWN rather than "armed" when the value is unreadable — naming which source it read. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GSg6k5yejGqUkCGVvxaKTF
…e changelogs
A diff3 base marker ("||||||| parent of <sha> (...)") was committed into two
changelogs instead of being resolved, putting an internal commit identifier
into published release history. Verified against the merge base that only the
marker line was spurious — no entry body was duplicated on either side.
The 0.4.0 / 0.3.0 / 0.17.15 entries also described behavior the review fixes
changed: the plausibility guard as sufficient for the pre-2.1.132 field
semantics, the setup check's conflated hook status, and the continuation
router's background-after-handoff ordering.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GSg6k5yejGqUkCGVvxaKTF
|
Claude finished @kyle-sexton's task in 6m 8s —— View job PR Review — context-guard 0.4.0 + session-flow 0.17.15
Codex findings — status at HEAD (
|
|
Claude finished @kyle-sexton's task in 5m 39s —— View job Security Review — PR #1603
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c23a2cae69
ℹ️ 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".
|
This was generated by AI (triage lane — raw-intake brief). Triaged at head VerdictReal and actionable. Genuine change, correctly linked ( Work classC4 — structural. Not a judgment call: the repo already labelled the primary linked issue GateHuman review AND human merge. Mandatory — no autonomous lane may merge this. Cross-cutting assessmentYes — this changes contracts other plugins depend on. Concretely:
State of playCI (head
Mergeability: Latent post-rebase failure (invisible in the current green rollup): the PR's new Review threads: 10 total, 0 resolved. All from Five live (
Five outdated — I re-verified each against
Already addressed per the PR body, and consistent with what I read: an author-run Next concrete actions
Blockers / open questions for a human
|
|
Conflict-resolution worker claiming this PR to resolve the |
Two conflicts, both session-flow, both a version-ordinal collision rather than a behavioral one. main shipped its own 0.17.15 (the #1687 pre-compute diagnosis) plus 0.17.16 (its correction), so the number this branch had claimed for the continuation router was taken. Both sides are composed: main's 0.17.16 and 0.17.15 entries are kept verbatim — 0.17.16's body cites 0.17.15 by number, so renumbering either would break that reference — and this branch's continuation-router entry moves to a new 0.17.17 heading above them, with plugin.json matched to it. A patch bump for an `### Added` entry follows this plugin's own precedent (0.17.12 through 0.17.14). skills/workflow/SKILL.md auto-merged; this branch contributes only additive routing (description, argument-hint, mode-table row, new step 4) and asserts nothing main's 0.17.16 rewrite deleted. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FtrbRf8pyuYmuWXodEPivm
…he setup skill scripts/validate-plugin-contracts.mjs forbids a marketplace-qualified pluginConfigs key anywhere under skills/setup/, and the hook-status step pointed at the operator's source of truth by writing one out literally. That is the sole plugin-gate failure on this branch; every contract test already passed. The sentence names the same source of truth without binding it to a marketplace name, matching how every other setup skill in the repo refers to a bare `pluginConfigs`. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FtrbRf8pyuYmuWXodEPivm
|
Claude finished @kyle-sexton's task in 5m 1s —— View job PR Review — context-guard 0.4.0 + session-flow 0.17.15
plugin-gate fix (
|
| Concern | Status |
|---|---|
plugin-gate CI failure |
✅ Fixed by 04e681c |
| Merge conflict (CONFLICTING/DIRTY) | ❓ Still needs rebase — 10 commits behind at triage time |
Latent hook-utils-sync failure post-rebase |
❓ Not yet rebased, so not yet triggered |
| Thread 1 (token band version gate) | ✅ Fixed in code; close the thread |
| Thread 2 (capability table reconciliation) | ✅ Fixed in code; close the thread |
| Thread 3 (background before Q1) | ❌ Open — needs a code change |
| Thread 4 (inject on persist failure) | ❌ Open — needs a code change |
| Thread 5 (telemetry ok on write failure) | ❌ Open — needs a code change |
| Glob pattern in portability registry check | Minor — safe today, should fix before merge |
Threads 3–5 are correctness defects, not style. Thread 4 in particular re-fires in a degraded context at the worst possible time. All three require a code change; the thread comments must also be resolved before merge per AGENTS.md.
|
Claude encountered an error after 4m 28s —— View job Security Review — PR #1603
|
|
🤖 Lane claim released — babysit-loop cycle |
|
Merge-lane cycle 2 claim. Working this PR now. |
|
Merge-lane cycle 3 complete for this PR. Conflict resolved and pushed ( Both sides' intent recovered from history before touching any hunk:
Also fixed the latent Checked the version-lane gap the parity gate can't itself detect (head version equal to main's would silently skip the gate): all three touched plugins (session-flow 0.17.17, context-guard 0.4.0, plugin-quality 0.3.0) are properly bumped above current main — no silent collision. CI: all 31 checks pass, including Judgment calls left for human review/merge (not touched — mechanical scope only):
No merge or thread-resolution action taken — resolution and CI fixes only, per this lane's authority. |
|
Releasing this claim — found the conflict-resolution redo already completed and pushed by a concurrent run under the same lease instance (see the cycle-3 completion comment above, head now |
|
Merge-lane cycle 5 (renewed) — prior claim lapsed while running the context-guard test suites (all green). Head still |
…kground-continuation ordering Addresses 3 of the 10 open P2 automated-review threads on this PR that were still live in code (the other 7 were already fixed by earlier commits on this branch and only needed thread resolution): - zone-crossing-inject.sh: a zone-state persist failure was swallowed (`|| true`), so the hook fell through and re-injected the ~1KB guidance block on every subsequent PostToolBatch/UserPromptSubmit instead of once per transition. Now emits telemetry status:error and exits immediately (fails open silently — no additionalContext) on a persist failure. - post-compact-mark.sh: a failed temp-file write or atomic rename was swallowed and telemetry still reported status:ok. The write-and-rename result is now tracked and telemetry reports error on either failure path; the hook still always exits 0 (PostCompact has no decision control). - continuation.md: the explicit-background-continuation question was ordered after the zero-cost continue-in-session question, so it was unreachable whenever context was healthy, silently discarding an explicit user request. Hoisted to run immediately after the machine-going-away check, before every cost-based question. Regression tests added to both hook test suites (zone-crossing-inject and post-compact-mark); the post-compact-mark case probes for POSIX directory write-mode enforcement and skips honestly on filesystems that don't have it (e.g. Windows ACL volumes under Git Bash) rather than false-failing. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 6m 9s —— View job Security Review — PR #1603
|
|
Claude finished @kyle-sexton's task in 3m 9s —— View job PR Review — context-guard 0.4.0 + session-flow 0.17.17
Latest commit (
|
| Thread | Fix commit | Status |
|---|---|---|
| 1. Token bands version gate | 89f63d9 |
Code fixed; thread needs close |
| 2. Capability table reconciliation | 0ae26b3 |
Code fixed; thread needs close |
| 3. Background ordering before Q1 | d8d281f |
Code fixed; thread needs close |
| 4. Inject on persist failure | d8d281f |
Code fixed; thread needs close |
| 5. Telemetry ok on write failure | d8d281f |
Code fixed; thread needs close |
| 6–10. (Outdated — merge markers, race, octal, kill switch, etc.) | Earlier commits | Code fixed; threads need close |
Overall assessment
All code-level correctness defects from the 10 P2 automated review threads are resolved at HEAD. The three fixes in d8d281f are correct and each comes with a targeted regression test. Version lanes are clean (context-guard 0.4.0, session-flow 0.17.17, plugin-quality 0.3.0). The one remaining item before merge is the check-shell-portability.sh:100 glob-pattern one-liner — safe today but explicitly flagged "should fix before merge" in the triage.
C4 human-merge gate applies regardless of CI state. Per the triage and work-classes.md, this PR requires human review and human merge; the evidence-predicate table records no autonomous-merge path for C4. All 10 discussion threads must be resolved (not just code-fixed) before merge per AGENTS.md.
… failure The post-compact-mark persist-failure regression test added in d8d281f could never observe the failure it asserts, so plugin-gate failed with "telemetry status not error". Two defects, one on each side: - post-compact-mark.sh: `mv -f "$tmp" "$target"` onto an existing DIRECTORY SUCCEEDS by moving the temp file inside it. The hook reported status:ok while consumers found nothing readable at the contract path and a temp file was stranded in that directory on every compaction — exactly the false "ok" the previous commit set out to eliminate. The rename is now refused up front (pre-`mv`, so nothing is littered) and telemetry reports error. - post-compact-mark.test.sh: the `chmod 555` simulation was structurally unobservable. The hook re-asserts `chmod 700` on its own contract directory on every run, healing the block before it writes; the test's probe ran before the hook, hiding it. Verified on Linux as a non-root user: mode 555 -> 700 and the marker written. Swapped to the directory-at-path idiom zone-crossing-inject.test.sh already uses — no permission bits, so the case now RUNS on Git Bash instead of skipping (13 pass + 1 skip -> 16 pass). The assertion is unchanged, and a third assertion pins the guard as pre-rename. The hook's `chmod 700` self-healing is deliberate hardening on a cross-plugin seam directory and is left intact. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 3m 55s —— View job PR Review — context-guard 0.4.0 + session-flow 0.17.17
New commit
|
| Thread | Fixed in | Still needs |
|---|---|---|
1. Token bands version gate (context-zone.sh:258) |
89f63d9 |
Thread closure |
2. Capability table reconciliation (reader-contract.md:140) |
0ae26b3 |
Thread closure |
3. Background ordering before Q1 (continuation.md:35) |
d8d281f |
Thread closure |
4. Inject on persist failure (zone-crossing-inject.sh:90) |
d8d281f |
Thread closure |
5. Telemetry ok on write failure (post-compact-mark.sh:88) |
d8d281f / 965da6f |
Thread closure |
| 6–10. (Outdated: merge markers, grace race, octal, kill switch, ordering) | Earlier commits | Thread closure |
Thread 5 now has two complementary fixes: d8d281f (track marker_ok, emit status: error) + 965da6f (pre-rename directory guard that prevents the mv-into-directory false-ok). Both should be cited when closing it.
Cleared — no new issues at 965da6f
- Directory guard logic is correct on all three code paths; no temp file stranded in any failure scenario.
rm -f "$tmp"in the directory-check branch is correct —$tmpand$targetare distinct paths, so the temp is cleaned without touching the blocked contract location.- Changelog entry correctly describes the fix without overstating scope.
- The portability checker self-test still exercises the correct registered-copy-exempt case (existing test at line 1192–1207); the new test I prepared adds the glob-vs-literal distinction as a distinct case.
- C4 human-merge gate from the triage is unchanged:
work-classes.mdrecords no autonomous-merge path for C4; human review and human merge remain mandatory.
|
Claude finished @kyle-sexton's task in 4m 46s —— View job Security Review — PR #1603
|
# Conflicts: # plugins/session-flow/CHANGELOG.md
# Conflicts: # plugins/session-flow/CHANGELOG.md
|
Claude finished @kyle-sexton's task in 3m 30s —— View job PR Review — context-guard 0.4.0 + session-flow 0.17.x
Latest merge commit (
|
| # | Finding | Fixed in | Thread resolved |
|---|---|---|---|
| 1 | Token bands version gate | 89f63d9 |
✅ Resolved |
| 2 | Capability table reconciliation | 0ae26b3 |
✅ Resolved |
| 3 | Background ordering before Q1 | d8d281f |
✅ Resolved |
| 4 | Inject on persist failure | d8d281f |
✅ Resolved |
| 5 | Telemetry ok on write failure | d8d281f + 965da6f |
✅ Resolved |
| 6–10 | Merge markers, grace race, octal parse, kill switch, ordering | Earlier commits | ✅ Resolved |
Overall assessment
All code-level correctness and security findings are addressed. The one remaining item before merge is the one-line [ ] vs [[ ]] fix above — safe today (registry has no metacharacters) but a documented footgun that has been flagged in every review pass since this PR's CI exemption code was introduced.
C4 human-merge gate remains in effect. Per the triage and work-classes.md, this PR requires human review and human merge regardless of CI state. The triage also identified scope questions for a human to decide (repo-wide CI gate change bundled into a feature PR, token-band threshold ratification, blocking-mode carve-out sufficiency).
| Branch: feat/1475-1476-zone-hooks-and-router
|
Claude finished @kyle-sexton's task in 5m 30s —— View job Security Review — PR #1603
Reviewed HEAD: Scope: all 33 files changed in this PR, with focused analysis on the new hook scripts, the resolver, and the portability-checker change. Finding 1 — IMPORTANT / CONFIRMED
target=$(jq -r '(.tool_input.file_path // .tool_input.notebook_path // "")' <<<"$INPUT" 2>/dev/null) || target=""This uses a bash here-string (
What can go wrong: Security impact: The deadlock occurs inside the handoff-writing exemption check — the very path that is supposed to guarantee no-deadlock-by-construction. A large Write whose
Even for non-handoff writes, the hang converts the gate's intended "deny + telemetry" behavior into a frozen session with no output. This is the most damaging mode because subsequent calls (including handoff writes) queue behind the frozen hook. Trigger condition: Fix: replace the here-string with the safe pattern already in use everywhere else in this file: # Before
target=$(jq -r '(.tool_input.file_path // .tool_input.notebook_path // "")' <<<"$INPUT" 2>/dev/null) || target=""
# After
target=$(printf '%s' "$INPUT" | jq -r '(.tool_input.file_path // .tool_input.notebook_path // "")' 2>/dev/null) || target=""Finding 2 — SUGGESTION / CONFIRMEDRegistry comparison uses Bash glob matching —
[[ "$rel" == "$line" ]] && return 1In Bash This finding has been raised in every security review pass on this PR (three runs at Finding 3 — SUGGESTION / PLAUSIBLE
STATE_DIR="${CLAUDE_PLUGIN_DATA:-${HOME:-.}/.claude/context-guard}/state"When both The concern is multi-tenant or containerised environments where Also raised in two prior security review passes; still open at HEAD. Cleared at HEADThe following were re-verified and found clean:
SummaryOne IMPORTANT new finding: the here-string pattern at |
The merge of origin/main brought a newer lib/hook-utils.sh (the 8.3 short-name expansion for Windows membership checks). Every other plugin's copy travelled with it; context-guard's — added by this branch — did not, so the byte-identity gate that owns registered sync copies failed. Regenerated with scripts/sync-hook-utils.sh rather than hand-edited: the copy is a materialization of lib/, and the sync script is the only path that keeps it exact. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 9m 29s —— View job PR Review — context-guard 0.4.0 + session-flow 0.17.x
New commit
|
| Item | Status |
|---|---|
CI / plugin-gate / hook-utils-sync / cross-plugin-source-drift |
✅ All green at bbedb39 (prior runs) |
hook::expand_8dot3 new function |
✅ Correct and secure |
| All 10 P2 review threads | ✅ Code-fixed and resolved |
zone-gate.sh:87 here-string deadlock |
❌ Still present — fix prepared, push denied |
check-shell-portability.sh:100 glob matching |
❌ Still present — fix prepared, push denied |
| C4 human-merge gate | ❌ Mandatory per work-classes.md — no autonomous path exists |
The two remaining items are the only technical blockers. Both are one-line changes with co-located repro-first regression tests. All prior code-review and security-review findings are resolved in code and thread.
| Branch: feat/1475-1476-zone-hooks-and-router
|
Claude finished @kyle-sexton's task in 4m 22s —— View job Security Review — PR #1603
Reviewed commit: Scope: the single file changed in New commit
|
… back to CWD
Both stateful hooks resolved their state root as
`${CLAUDE_PLUGIN_DATA:-${HOME:-.}/.claude/context-guard}`, so with neither
variable set the blocking gate's grace counter and the injector's last-seen
zone landed under `./.claude/` — relative to whatever directory the hook
process started in. A counter that resets with the working directory is not a
budget, and a last-seen zone that moves with it cannot hold the
once-per-transition contract. Both now require an explicit root and exit 0
without one, matching what post-compact-mark.sh already does for its marker
path.
Also routes zone-gate's handoff-exemption path extraction through
hook::jq_field, as the file's other two extractions already do. Reported as a
here-string deadlock; that did not reproduce — a 200KB here-string completes
on bash 5.3.9 (Cygwin), which routes an over-capacity here-string through a
temp file rather than a pipe. Changed for consistency with the file's own
helper, which also CR-strips, not as a hang fix, and the new 70KB case is
named for what it actually covers: the chunked payload drain delivering a
large tool_input intact to the exemption.
Adds a repro-first case pinning the portability checker's registry comparison
as literal: a `hooks/*.sh` entry must exempt nothing. The comparison's RHS is
quoted, so globbing is already inactive (verified: the case fails only when
the quotes are dropped) — the test locks that in against a future edit.
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 2m 56s —— View job PR Review — context-guard 0.4.0 + session-flow 0.17.x
New commits since last review (
|
| Item | Status |
|---|---|
| All 10 P2 automated-review threads | ✅ Code-fixed and resolved |
${HOME:-.} CWD fallback (SUGGESTION) |
✅ Fixed in 1af4e4b |
zone-gate.sh here-string extraction (IMPORTANT) |
✅ Routed through hook::jq_field in 1af4e4b |
check-shell-portability.sh:100 glob (SUGGESTION) |
✅ Prior finding was incorrect; test pins correct behavior |
| CI / all gates | ✅ Confirmed green per prior runs; no scope changes that would reopen them |
| C4 human-merge gate | ❌ Mandatory per work-classes.md — no autonomous lane may merge this |
No technical blockers remain. The C4 designation (cross-cutting contract change, repo-wide CI gate modification, two migrated consumers) requires human review and human merge per work-classes.md. The triage also identified scope decisions for a human to confirm: whether to split the check-shell-portability.sh change into its own PR, ratification of the declared token-band thresholds, and confirmation that the blocking-mode exemption carve-out is sufficient.
| Branch: feat/1475-1476-zone-hooks-and-router
|
Claude finished @kyle-sexton's task in 5m 28s —— View job Security Review — PR #1603
Reviewed commit: Scope: all 33 files changed in this PR vs Prior findings — disposition at HEADIMPORTANT / CONFIRMED (prior review at Fixed. SUGGESTION / PLAUSIBLE (prior reviews) — Fixed in commit if [[ -n "${CLAUDE_PLUGIN_DATA:-}" ]]; then
STATE_DIR="$CLAUDE_PLUGIN_DATA/state"
elif [[ -n "${HOME:-}" ]]; then
STATE_DIR="$HOME/.claude/context-guard/state"
else
exit 0
fiNo SUGGESTION / CONFIRMED (prior reviews) — False positive — correctly debunked by the author and confirmed here. In bash New commits since last review
Analyzed above. The change affects only the
Merge-only commit, no source changes. Full-diff security verification at HEADThe following surfaces were re-verified across the full PR diff:
FindingsNo CRITICAL or IMPORTANT security issues found at HEAD |
Review-body findings addressed at
|
…1544) *This was generated by AI during work-loop execution.* ## Summary Enables the two shell-portability-lint classes #1510 staged for this PR — `date -d` and `stat -c`. (The issue's third class, `mktemp -p`, went active separately in #1543 while this branch was open, so the token file's STAGED section is now empty.) - **Precision fixes to the staged regexes.** The original patterns matched `date`/`stat` as bare substrings, so `[[ -d "$candidate" ]]` (via "can-**DATE**") and `git -c alias.x=status -c ...` (via "**STAT**us") false-positived. Both now require whitespace immediately after the command name. - **Extended `is_guarded()`** with a same-line `stat -c` / `stat -f` guard requiring an actual `||` fallback relationship, matching the rigor #1519/#1534 established for the `readlink`/`realpath` guard. - **Ran `scripts/check-shell-portability.sh --all`** per the issue's step 4 and resolved every real hit from the two newly-active classes: - `portability-ok:` annotations on already-correct dual-dialect date/stat call sites in `claude-ops`, `context-guard`, `kindle-dedrm`, `work-items` (most span a line break or an if/else block, so the same-line auto-guard cannot recognize them even after extension); - a genuine fix for one previously-unguarded gap: `skill-quality`'s vendor-sync-age check had no BSD `date` fallback at all and silently no-op'd on macOS; - Windows-only-script annotations for `kindle-dedrm`'s two `stat -c` sites. - **Pre-existing violations of already-active classes** surfaced by touching `skill-quality/scripts/check-skill.sh` (GNU-only `\S`/`\b` escapes in its own `grep -qE` patterns) were fixed so the PR's own diff stays clean. - Every touched plugin's version is bumped with a matching CHANGELOG entry. ## Scanner correctness work (review rounds) Codex review found defects in the scanner itself across several rounds. Every one is addressed here — all but one fixed, and that one recorded as designed behavior. The first five: | Reported shape | Direction | Resolution | | --- | --- | --- | | `stat ${x:-$((1 \| 2))} -c %s` read clean | fail-open | Fixed — arithmetic expansion is its own mask state with per-frame paren-depth tracking, so `$((` is no longer consumed as `$(` plus a stray `(` | | `x=$(stat -c …) y=$(true) \|\| stat -f …` read as a guarded ladder | fail-open | Fixed — `status_swallowed()` now establishes that the matched frame is the *status-determining* frame of its command, rather than excluding one neighbour shape at a time | | `d"a"te -d …` / `st"a"t -c …` read clean | fail-open | Fixed — command names are spelled letter-by-letter with optional quote runs between them, since quote removal splices the word before the utility sees argv | | A quoted word spanning physical lines hid its option | fail-open | Fixed — records join on an unterminated quote as they already did on a dangling backslash, with every escape attributed to the physical line the hit sits on | | A utility named in a string (`echo "run date -d tomorrow"`) is reported | false positive | **Not fixed — documented.** Recorded in the script header as the gate's largest accepted over-flag | On the last row: matching text the shell would treat as a string literal is the whole mechanism behind the regex-escape classes, where `grep -E "\bword"` lives inside quotes and must still be caught. Requiring command position for the option-based classes alone needs a per-class axis in the token data plus word-level tokenization, and every partial answer trades this false positive for a fail-**open** — the same trade already made and withdrawn for `--` (see the block above `collapse_subs()`). `portability-ok:` is the one-line escape. This is the same decision already taken once in this file, now written down rather than left implicit. Two further defects were found and fixed while closing the quote-join finding, both pre-existing: - **Heredoc bodies leaked quote state.** A stray backquote in a PowerShell settings body (``"CustomRule`Path"``) opened a frame that, once joining was active, swallowed the 57 lines after it. Heredoc bodies are now excluded from joining — they are data, so they can neither continue a command nor leave a quote open — while still being scanned, since this corpus writes real scripts through heredocs. - **A `#` opening a joined physical line did not start a comment**, so a commented-out `|| stat -f` could excuse a hit above it. A newline now joins `WORDSTART`. The security-review lane then found a third, in the gate's own plumbing: a relative `SHELL_PORTABILITY_TOKENS` path shaped like `identifier=value` is parsed by awk as a variable assignment rather than opened, so no class loaded, every file reported clean, and awk still exited 0 — invisible to the scanner-fault check. It now gets the same `./` disambiguation the scanned file already had, and an empty pattern set fails closed however it arose. A further review round then found six more, five of them pre-existing and one a regression from the quote-join above. Rather than answer them one at a time — the pattern that had been producing a fresh variant every round — they were taken as three families and generalized: - **Quote spellings the token classes did not admit.** A backslash quotes exactly as a quote pair does, so the quote-run class is now `['"\]` in every place the command word, the short-option cluster and the long option are spelled — closing `da\te -d`, `date -\d`, `date "--date"`, `date --"date"=` and `stat --"format"=` together. `&>` / `&>>` join the separator class after the command name, since bash runs `date&>/dev/null -d tomorrow` with the GNU-only option. - **Boundaries that predate records containing a newline.** A structural newline ends a command inside a `$( )` frame, so it now bounds the guard's segment gap and the lookback both guards share. That lookback became a backward scan rather than a greedy `.*[;|&)]` match, because whether `.` matches a newline is an awk-implementation difference this gate must not rest on. **This closes the one regression the quote-join introduced**: `x=$(stat -c …` newline `true) || stat -f …` had read as a guarded ladder. - **Frames still not tracked.** A raw subshell inside a command substitution was not pushed, so its closing paren popped the substitution — the same unbalanced-frame failure the arithmetic branch fixed, one spelling over. A `)` with no frame open remains a `case` pattern terminator. Also in that round: a spaced redirection operand (`|| 2> /dev/null stat -f …`) is no longer rejected as a non-ladder, and the whole-file `portability-scope:` declaration moved out of a grep pre-pass into the awk program. A grep sees no shell structure, so it honored the token inside a heredoc **body**, where the line is generated data rather than a declaration the file makes about itself — one such line silently exempted a whole file. A final round found the same quote family reached through Bash ANSI-C (`$'…'`) and locale (`$"…"`) quoting: `d$'a'te -d`, `date -$'d'`, `stat -$'c'`, `st$'a't -c` and `date $"--date"=` all reach the GNU utility while reading clean. A quote-run element is now `(\$?['"]|\\)` — an optional `$` before a quote, or a backslash — defined once and shared by the command word, the short-option cluster, the long option, and the fallback guard. A **bare** `$` is deliberately excluded, since `$config` is a variable expansion rather than quote removal: `validate -d $config` stays clean and `d$a$t$e` is not a spelling of `date`, both pinned as negatives. Moving the scope decision into awk then turned out to have fixed only the heredoc half of its own problem: the check still read the raw record without asking what earlier lines had left open, so a physical line spelling `# portability-scope:` inside a multiline quoted value or substitution granted whole-file scope and suppressed every hit in the file. The marker now counts only on a line that also *opens* its own record — the one context where a leading `#` starts a comment rather than being data. A genuine declaration is unaffected, and the regression cases pin both directions, since the cheap fix here is one that quietly breaks the declaration it exists to protect. ## Token-file premise correction (rode along) The `mktemp -p` rationale comment asserted BSD/macOS mktemp "has no `-p`". It does — FreeBSD 14.2 and Apple both document `-p tmpdir, --tmpdir[=tmpdir]`. The real hazard is **precedence, and it diverges silently**: GNU treats `-p` as authoritative and overrides `TMPDIR`, while BSD/macOS consults it only as a fallback when `TMPDIR` is unset, so the same command writes to different directories per platform with no error either way. The gate's *behavior* was already correct; only its stated reason was wrong. Carried here because this PR owns the token file. The plugin CHANGELOG entries that quoted the old sentence are historical and left alone. ## Test plan - [x] `bash scripts/check-shell-portability.test.sh` — **215/215 passing**, including new regression cases for every shape above (arithmetic-expansion frames, sibling-substitution status ownership, quote-spliced command words on both rungs of a ladder, quoted words spanning lines, per-physical-line attribution and annotation scoping, heredoc-body isolation, and the joined-line comment opener). - [x] `scripts/check-shell-portability.sh origin/main` (this PR's own diff, 15 shell files in scope) — clean. - [x] `scripts/check-shell-portability.sh --all` — **19 hits, the same hits `origin/main`'s own scanner reports over the same tree**, all from unrelated already-active regex-escape classes and none from the two newly-active ones. Every scanner change above was held to that comparison, so no fix introduced a false positive anywhere in the corpus. One hit is attributed to a different line than main reports it: this PR introduces logical-line joining, so a backslash-continued record is now reported at its first physical line, as the script header specifies. That joining is also what makes a `date` whose `-d` sits on the next continued line reportable at all — main reads that shape clean. - [x] Full test suites for every touched script pass: `morning-brief.test.sh`, `claude-observability.test.sh`, `context-zone.test.sh`, `statusline-tee.test.sh`, `lease.test.sh`, `check-skill.test.sh`. - [x] `shellcheck --rcfile=.shellcheckrc` on every changed `.sh` file — clean. - [x] `scripts/validate-plugins.sh` — all manifests + catalog validate. - [x] `scripts/check-changelog-parity.sh --check-bump origin/main` — every version-bumped plugin has a matching CHANGELOG entry. ## Related - Closes #1510. - #1491 — original shell-portability-lint gate. - #1543 — activated `mktemp -p`, the issue's third class, independently of this PR. - #1528 — the deferred `mktemp -p` migration; closed. - #1562 — `--` end-of-options handling, which shares the word-level tokenization the command-position over-flag documented above would also need. - Rebased onto #1519 / #1534 / #1530, which merged mid-session and changed the same `check-shell-portability.sh` / `shell-portability-tokens.txt` files. Merged with `origin/main` again after #1603 / #1751 / #1752 landed; `context-zone.test.sh` takes main's side whole, since main replaced the unsuffixed `sed -i` this branch had annotated with a genuinely portable form. --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Closes #2972 The continuation router had zero eval coverage: five workflow cases existed and none exercised the `continue` path, while one of its ordering invariants had already regressed once and been fixed inside the router's own creation PR (#1603, originating issue #1476). The 0.27.0 router evolution (#2971 / PR #3029) shipped without it by design; this is the safety net that item deferred. Nine cases (ids 6-14) pin the shipped behavior. Each grades the router's stated reason, not just its verdict, so a rewrite that reaches the right mechanism by the wrong edge still fails. - First-yes-wins: machine-going-away routes to clean-stop over a healthy-context continue; an explicit background request outranks question 3's zero-cost in-session exit (the regressed invariant). - Zone gating: a compaction marker overrides a green zone word and rejects in-session continue for judgment-heavy work; a healthy zone whose next stage consumes the reasoning verbatim prefers continue. - Post-evolution: the AFK edge hands the spawn brief to orchestrate, keeps asking (non-terminal), and launches nothing; suggest-by-default is graded on its full mechanism/evidence/next-step shape. - Autonomy gating: the opt-in counts only in a genuine user turn; `auto` cannot carry out /clear or /compact; clean-stop takes the literal `continue auto` token and nothing else. Schema-validated and quality-linted green (0 warnings). Co-authored-by: Claude <noreply@anthropic.com>

Closes #1475
Closes #1476
What
One branch for the companion pair, per both issues' "likely one PR" note.
context-guard 0.4.0 — zone-crossing hooks + window-class token bands (#1475).
hooks/hooks.jsonregisters four fail-open handlers, each with a co-located contract-test suite:zone-crossing-inject.sh(PostToolBatch + UserPromptSubmit; injects continuation guidance viaadditionalContextonce per transition into a worse zone — silent on unchanged/improving/unknown; PostToolBatch's once-per-batch semantics replace the per-tool dedupe a PostToolUse design would have needed),zone-gate.sh(PreToolUse overWrite|Edit|NotebookEdit|Agent|Workflow; active only inblockingmode on a fresh dumb-zone snapshot past a grace budget; handoff-path writes, reads, Bash, and Skill never gated — no deadlock by construction),post-compact-mark.sh(PostCompact; persists the evidence-degraded marker, re-arms the grace budget, prunes stale markers).used_percentage(distance to compaction; upstream computes it input-only) and window-class token bands over occupancy =total_input_tokens + total_output_tokens(distance to quality loss; 200k class 100k/160k, 1M class 200k/400k — declared judgment defaults, provenance table on context-guard: zone-crossing hooks (advisory/enforcing) + window-class token bands #1475). Combination rule stated verbatim in the reader contract; plausibility guard covers the pre-2.1.132 cumulative token-field semantics;zones.jsongains an optional per-shape-validatedtoken_bands(absent = zero-config, every v1 file keeps working); percentage keys retained with a recorded retirement trigger.docs/conventions/hook-config-deliverychannel B with in-script defaults; three newuserConfigoptions; telemetry producers registered with data schemas; both known inliners migrated (plugin-quality (now 0.3.0) context-gate + setup seeder) and the reader contract's promised consumer drift-check lane now actually exists (plugin-quality/scripts/zones-inline-drift.test.sh, runs in plugin-gate).session-flow 0.17.19 — workflow end-of-phase continuation router (#1476).
context/continuation.mdspoke +continueargument mode + a default-mode phase-boundary step. Outcome set derived from the mechanisms the plugin actually installs plus built-ins (continue //clear/ handoff / continue-in-background / clean-stop //compact);reconcile/orientdeliberately non-terminals; every ordering edge carries its stated purpose (machine-loss asked first, zero-cost exits before writing mechanisms, compact last as the model-written lossy summary at the least-intelligent point — tradeoff pointer to handoff's "Fork beats compaction when the window is deep", not a restatement). Zone input presence-gated on the reader contract with no inlined band values; honors the evidence-degraded marker. Documents the worker handoff-relay convention (worker writes its own handoff, returns the path only).CI fix (needed by this PR, correct independently):
check-shell-portability.shnow exempts registered cross-plugin sync copies (scripts/cross-plugin-source-registry.txt) — a syncedhooks/hook-utils.shcopy can never carry a local fix or annotation, so the source is where the gate belongs; surfaced by this PR adding the first new copy since the GNU-regex-escape tokens went active (line 1114's bash glob[\<\>]is not a GNU regex escape). Repro-first suite case included.Local review outcomes (obtained before PR creation)
40aa5b69with failing-test-first repros, plus 2 suspicions it checked and cleared itself. (1) PostCompact marker silently unwritten for realistic payload sizes (measured 80KB+ lost to the single bounded stdin read on Git Bash pipes) → all three hooks now use a plugin-local chunked drain loop (hooks/payload.sh, the tee's provenread -Npattern) with 130–150KB regression tests; (2) same root cause failed the blocking gate open for exactly the biggest writes (verified: 2.8KB Write denied, 133KB Write allowed) → same fix + regression test; (3) the evidence-degraded marker was write-only and the grace reset let compaction disarm the gate → both zone consumers now treat a marked session as effectively dumb regardless of the resolved word, per the contract's own rule; (4) large batch payloads could skip injections → chunked reader; (5).compactedmarkers never pruned → 14-day sweep matching the tee's. An earlier same-content review pass on the pre-rebase branch had also run every repo gate itself (portability incl. its 108-case self-test, silent-skips, changelog parity, validate-plugins, all five new/changed suites) — all pass.jq --arg, no reflection of untrusted payload content into the model channel, strict ISO gate beforedate -d, no dynamic command construction,umask 077/chmod 700on state writes, and the portability exemption opens no silent-divergence hole (byte-identity gate owns those copies). Deliberate non-findings recorded in the review outputs (gate-bypass shapes are quality-control, not a security boundary; documented fail-open).Verification
context-zone.test.sh73/73,zone-gate.test.sh24/24,zone-crossing-inject.test.sh18/18,post-compact-mark.test.sh16/16,statusline-tee.test.sh47/47,zones-inline-drift.test.sh11/11,check-shell-portability.test.sh109/109.validate-plugins.shclean;sync-hook-utils.sh --checkreports all 15 copies matchinglib/; portability vsorigin/main, silent-skip, cross-plugin-source-drift, plugin-manifest-presence, skill-leaf-names, and changelog-parity (--checkand--check-bump) all clean. The repo's ownplugin-gateon CI is the authoritative full-suite run.Review-queue drain
All ten Codex review threads fixed and resolved, each verified against the merged tree rather than taken on the commit message: the gate grace-counter race (replaced with an O_APPEND byte counter), the
08-as-octal grace budget, the pre-2.1.132 cumulative-token semantics (now gated on an explicitcli_versionfloor), the reader contract's capability table (now per-shape, so the table and the combination rule can no longer derive different zones), the setup skill conflating registration with activation, leftover diff3 merge markers, the unreachablecontinue-in-backgroundoutcome (explicit-background is now question 1), the injector's write-only zone state, and the PostCompact marker's falsestatus:ok.Three further findings arrived in a review body rather than as threads. Two were real and are fixed here: both stateful hooks fell back to a
.-relative state root, and zone-gate's handoff-exemption path extraction bypassed the file's own jq helper. One was a false positive — the portability checker's registry comparison quotes its RHS, so globbing is already inactive; a repro-first case now pins that. Details and the platform probe are in the PR comments.Related
🤖 Generated with Claude Code
https://claude.ai/code/session_018qLeNKmcBFxZgsfEksLomX