Skip to content

fix(context-guard): default-disable the zone-crossing injection on both wirings - #2049

Closed
kyle-sexton wants to merge 1 commit into
mainfrom
fix/context-guard-zone-inject-default-off
Closed

fix(context-guard): default-disable the zone-crossing injection on both wirings#2049
kyle-sexton wants to merge 1 commit into
mainfrom
fix/context-guard-zone-inject-default-off

Conversation

@kyle-sexton

Copy link
Copy Markdown
Contributor

Closes #2009

Summary

Flips the default for zone-crossing-inject.sh — the ~1KB continuation-guidance lecture with a four-option exit menu, wired twice (PostToolBatch + UserPromptSubmit) — from always-on to opt-in, via a new per-hook zone_crossing_inject_enabled userConfig boolean defaulting false. The script and both wirings stay shipped; consumers opt back in by configuring the option true via /plugin. zone-gate.sh and post-compact-mark.sh keep their prior defaults. context-guard 0.4.9 → 0.5.0.

Fix

  • Kill-switch mechanism: the plugin's only existing switch (context_guard_hooks_enabled) is plugin-wide and would have silenced the gate and the PostCompact marker too, so a per-hook zone_crossing_inject_enabled userConfig boolean was added (declared default: false). Per docs/conventions/hook-config-delivery fact 3, the declared default field is never delivered to hook processes, so the shipped behavior lives in an in-script default: zone-crossing-inject.sh now exits silently unless CLAUDE_PLUGIN_OPTION_ZONE_CROSSING_INJECT_ENABLED is configured true (channel B, decision rule 4 — non-safety advisory surface, same channel the master switch already uses). One in-script gate placed after the master switch covers both wirings; the master switch still outranks the opt-in.
  • Rationale (ablation): Hook-surface classification: 44 wired hooks classified policy vs behavioral (report-only) #2021's hook-surface classification found this hook to be the only always-on model-facing behavioral hook in a default install across the non-formatter plugins, and context-guard: the zone-crossing injection hands the model an exit menu, and check I23 now flags it #2009 records that check I23 flags its exit menu — the zone measurement decides only when to ask while the model still decides whether to stop, so the injection manufactures stop-initiative current models already carry. Config-off-first follows PLUGIN-PHILOSOPHY's instruction-economy evidence gate (kill switch exists → disable before delete).
  • Docs updated to stay truthful: plugin description, README (component bullet + Configuration), setup skill's hook-activation reporting step (new "Injection opt-in" fact; "injection hook still runs" corrected), CHANGELOG [0.5.0] entry, regenerated docs/CATALOG.md (cheatsheet generator ran clean with no diff).

Fresh-docs verification (fetched this session, 2026-08-08):

Verification

  • plugins/context-guard/hooks/zone-crossing-inject.test.shPASS 20 / FAIL 0, including new coverage: default (opt-in unset) is silent and stateless; explicit false silent; master kill switch outranks a true opt-in; all pre-existing scenarios opt in explicitly and still pass.
  • plugins/context-guard/hooks/zone-gate.test.shPASS 24 / FAIL 0 (unchanged file, run to confirm no collateral).
  • plugins/context-guard/hooks/post-compact-mark.test.shPASS 16 / FAIL 0 (unchanged file).
  • Repo gates run locally: check-silent-skips.sh clean, check-hook-userconfig-argv.sh clean, check-changelog-parity.sh --check and --check-bump origin/main clean, markdownlint-cli2 clean on the three edited markdown files, jq validates plugin.json, bash -n + shellcheck clean on both edited scripts.
  • node scripts/generate-catalog.mjs (diff committed) and node scripts/generate-cheatsheet.mjs (no diff).

Related

🤖 Generated with Claude Code

…th wirings

Add a zone_crossing_inject_enabled userConfig boolean (declared default
false, in-script default false per the hook-config-delivery convention's
fact 3 — the declared default is never delivered to hook processes) that
gates zone-crossing-inject.sh after the existing context_guard_hooks_enabled
master switch. One in-script gate covers both wirings (PostToolBatch +
UserPromptSubmit). The script and its wiring stay shipped; operators opt
back in via /plugin.

Rationale: #2021 classified this injection as the only always-on
model-facing behavioral hook in a default install, and #2009 (check I23)
flagged its four-option exit menu as manufacturing stop-initiative current
models already carry. Config-off-first per the plugin philosophy's
instruction-economy evidence gate. zone-gate.sh and post-compact-mark.sh
keep their prior defaults.

Tests: opt existing scenarios in explicitly; add default-off (unset and
explicit false) coverage and master-switch-outranks-opt-in coverage.
Version 0.4.9 -> 0.5.0; CHANGELOG entry; README, setup SKILL.md, and the
generated catalog updated to match.

Closes #2009

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@cursor

cursor Bot commented Aug 8, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@claude

claude Bot commented Aug 8, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 1m 8s —— View job


Security review complete

  • Gather context (PR diff, changed files)
  • Review each changed file for security issues
  • Post findings

No security issues found.

Scope reviewed (7 files, per gh pr diff): docs/CATALOG.md, plugins/context-guard/.claude-plugin/plugin.json, plugins/context-guard/CHANGELOG.md, plugins/context-guard/README.md, plugins/context-guard/hooks/zone-crossing-inject.sh, plugins/context-guard/hooks/zone-crossing-inject.test.sh, plugins/context-guard/skills/setup/SKILL.md.

Notes from the review (no findings, just what was checked):

  • The new gate in zone-crossing-inject.sh[[ "${CLAUDE_PLUGIN_OPTION_ZONE_CROSSING_INJECT_ENABLED:-false}" == "true" ]] || exit 0 — is a plain bracket string comparison on an env var, not interpolated into a command string, eval'd, or used in a path — no injection surface.
  • The new zone_crossing_inject_enabled userConfig boolean in plugin.json only narrows default behavior (injection now off-by-default); it doesn't widen any permission, alter hooks.json wiring, or touch the existing context_guard_hooks_enabled master switch's precedence — the new gate is placed after the master-switch check, so the master switch still outranks it, consistent with the PR's stated intent.
  • Existing hardening in the hook is untouched by this diff: session-id character-class validation (^[A-Za-z0-9_-]+$) used both for state-file path containment and hostile-input rejection, fail-open/silent-exit on missing HOME/CLAUDE_PLUGIN_DATA, umask 077 before state writes, and bounded/chunked stdin reads.
  • The other six changed files are documentation, changelog, catalog, and a bash test harness (adds an env var to opt tests into the new default-off behavior) — no executable logic changes with security relevance.
  • No changes to hooks/hooks.json, workflow files, permissions, or any network/credential-handling code in this diff.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b61d46c500

ℹ️ 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".

# Opt-in gate — default false (hook::check_enabled cannot express a
# default-off switch: it allows on unset). Covers both wirings
# (PostToolBatch + UserPromptSubmit); silent-skip-ok by design.
[[ "${CLAUDE_PLUGIN_OPTION_ZONE_CROSSING_INJECT_ENABLED:-false}" == "true" ]] || exit 0

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reset prior zone state before the first opted-in check

When an existing 0.4.9 session already has dumb in its persistent ${CLAUDE_PLUGIN_DATA}/state/<session>.zone, upgrading to 0.5.0 and later opting in while that session remains dumb produces no guidance: this early exit preserves the old state, so the first enabled invocation compares dumb to dumb and stays silent. That conflicts with the new test's stated intent that the disabled posture remain stateless so a later opt-in sees its first transition, and the user will receive nothing until the session recovers and worsens again. Clear or version the injection state while disabled, or explicitly detect the first enabled invocation.

Useful? React with 👍 / 👎.

@claude

claude Bot commented Aug 8, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 3m 39s —— View job


Review complete

Summary: This is a clean, well-scoped default-flip. The gating change in zone-crossing-inject.sh is correct and minimal — the new opt-in check ([[ "${CLAUDE_PLUGIN_OPTION_ZONE_CROSSING_INJECT_ENABLED:-false}" == "true" ]] || exit 0) is placed immediately after the existing master kill switch (hook::check_enabled "CONTEXT_GUARD_HOOKS") and before any snapshot/state work, so the master switch still outranks the opt-in and opted-out invocations pay no extra cost. hook::check_enabled's shared helper (hooks/hook-utils.sh:19-24) defaults to on when unset, which genuinely can't express default-off — the PR's inline check rather than a helper change is the right call here. plugin.json's new zone_crossing_inject_enabled option is correctly declared, and the CHANGELOG/README/SKILL.md updates accurately reflect the new default-off behavior and correctly cite docs/conventions/hook-config-delivery fact 3 (verified: that file does document the declared-default-not-delivered-to-hook-processes fact this design leans on).

Test coverage: zone-crossing-inject.test.sh adds direct coverage for the new default (test 0: opt-in unset → silent, stateless), explicit false (0b), and kill-switch-outranks-opt-in (6), and every pre-existing scenario was updated to opt in explicitly so its original assertion still holds. I did not execute the suite myself (no tool access to run scripts in this environment), so the PR's "PASS 20/FAIL 0" claim is author-reported and unverified by me — but reading the test logic, the assertions match the script's actual gating order.

Two stale-doc findings (not on changed lines, so noted here rather than inline):

  1. plugins/context-guard/hooks/zone-gate.sh line 9 — the posture-contract comment says "advisory (default): this gate is inert — guidance comes from the advisory injection hook (zone-crossing-inject.sh)". That's no longer accurate: after this PR, a default install has the injection hook silent too (zone_crossing_inject_enabled unset), so in advisory mode neither the gate nor the injection produces guidance out of the box. This file wasn't touched by the PR, so it wasn't updated alongside the behavior change — worth a follow-up tightening the comment to note the injection is itself opt-in since 0.5.0, so a reader debugging "why is there no guidance" doesn't get pointed at a hook that's also silent by default.

  2. plugins/context-guard/reference/reader-contract.md lines 177-180 — the "Zone-crossing hooks (first shipped consumer)" section still describes the advisory injection as if it fires unconditionally ("once per transition into a worse zone, inject continuation guidance... Silent while the zone is unchanged, improving, or unknown") with no mention of the new zone_crossing_inject_enabled gate. This is the plugin's own authoritative consumer-contract doc, and the PR's stated goal was updating docs to "stay truthful" — this file is a gap in that sweep since it wasn't in the changed-files list.

Neither of these blocks the PR (they're pre-existing files this change didn't touch, and the shipped behavior itself is correct), but both are genuine truthfulness gaps the "opt-in" change created in adjacent unchanged docs, worth a fast follow-up commit.

No other correctness, architecture, or error-handling issues found in the changed files. The kill-switch/opt-in layering, the version bump (0.4.9 → 0.5.0, correctly minor for a default-behavior change with no contract removal), and the docs updates that were in scope for this PR are all consistent and accurate.

@github-actions

github-actions Bot commented Aug 8, 2026

Copy link
Copy Markdown

Claude has reviewed this PR 1 time. The lane skips further automatic reviews after 5; deleting this comment resets the count.

@kyle-sexton

Copy link
Copy Markdown
Contributor Author

Superseded by #2031 (created by a concurrent session before this PR's spec was written, discovered after both were up). #2031 answers the same #2009 finding by splitting the injection by audience — the four-option menu moves to systemMessage (human channel) and additionalContext carries only the zone measurement plus a counter-steer — with the same context-guard 0.4.9→0.5.0 slot and the same files, so the two PRs conflict directly. Under the classification rubric this work-stream just codified (PR #2033), the reworked hook is a measurement relay with a non-derivable oracle — a keep, not the behavioral ablation candidate the original #2021 line-i classification described, because that classification was of the old payload. The open codex thread here (stale persisted zone state on the upgrade path) does not apply to #2031's shape, which keeps the hook always-on. #2021's line-i disposition for the context-guard leg is therefore #2031, recorded on the issue; the two guardrails injector legs still land via #2058.

@kyle-sexton kyle-sexton closed this Aug 9, 2026
@kyle-sexton
kyle-sexton deleted the fix/context-guard-zone-inject-default-off branch August 9, 2026 01:45
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.

context-guard: the zone-crossing injection hands the model an exit menu, and check I23 now flags it

1 participant