Context
Menu item #5 of the Boris-talk digest (knowledge-corpus, boris-cherny-we-cut-80-of-claude-code-s-qyPCVqFUyDo): hooks are officially carved out as the durable deterministic tier, BUT hooks that exist to correct model behavior (rather than enforce policy) are exactly what newer models obsolete. This is the classification pass that item called for — audit posture, report-only. Remediation is deliberately NOT included: the classification authority question (one-time audit PR vs standing playbook rubric — digest questions.md #5) is an operator decision this issue exists to inform.
Method: five parallel fresh-context read-only agents, one per plugin group, each reading hooks/hooks.json wiring first and every wired non-test script in full. Classification axes: mechanism (deny-gate / context-injection / deterministic-transform / notification-infra) and class (policy = invariant you'd keep with a perfect model; behavioral = corrects model behavior a better model gets right unaided; hybrid = both, with the split named).
Population note: the digest baseline said "108 hook scripts". Live count on main (19a3f7b3): 18 hook-shipping plugins, 66 non-test .sh hook scripts + 2 Python (disk-hygiene, wired from skills/clean/scripts/) + 2 project-local .claude/hooks scripts; 126 files under plugins/*/hooks/ including tests and sourced libs. The 108 figure appears to have counted test files; wired hook ENTRIES examined here: 44.
Roll-up
| verdict |
count |
entries |
| Behavioral — clean ablation candidates |
4 wirings / 3 scripts |
context-guard zone-crossing-inject.sh (wired twice: PostToolBatch + UserPromptSubmit); guardrails flag-commit-pr-skill-bypass.sh; guardrails workflow-resilience-check.sh |
| Hybrid — partial candidates (trim or narrow, don't delete) |
8 |
context-guard zone-gate.sh (predicate is model-competence; deny path inert by default); autonomy lane-stop-gate.sh (lecture text behavioral, provenance hardening policy); guardrails hardcoded-path-check.sh; guardrails block-noncanonical-commit.sh (narrow to actual-newline -m); markdown-format (coaching strings only); typos-format (wrapper prose only); source-control pr-body-linkage-gate.sh + pr-linkage-mcp-gate.sh (latency mirrors of a required CI check that remains the authority) |
| Policy / infra — keep |
remaining ~32 |
all guardrails deny-gates (secrets, dangerous-git, no-verify, hook-bypass, convention), the 3 guardrails ground-truth verifiers (cli-flag / skill-reference / stale-path — behavioral purpose, non-derivable oracle), all 8 claude-ops observability emitters, all formatter transforms + their 2 embedded code-execution trust gates (markdown-format, powershell-format), worktree-create-gate, disk-hygiene engine gate + monitor, desktop-notification, rate-limit-guard recorder, session-start provisioning, post-compact-mark, observer-arm |
Headline: in a default install, context-guard's zone-crossing-inject.sh is the ONLY always-on model-facing behavioral surface across the non-formatter plugins (autonomy's gate and the zone deny-path are default-off/advisory). It is also the subject of the already-filed #2009 (I23 flags its exit menu). Highest-value, lowest-risk ablation target; the two guardrails injectors (flag-commit-pr-skill-bypass, workflow-resilience-check) are the next two — static prose, no external oracle, kill-switchable without deletion.
Key nuance the policy/behavioral split surfaced: several hooks have a behavioral purpose but a policy-grade oracle — guardrails' cli-flag-verify (diffs written flags against the local binary's live --help), skill-reference-verify (globs the live plugin tree), stale-path-verify (git-history disappearance query). They correct hallucination, but with machine ground truth no model can know unaided. These are keeps; the report separates them from prose-only injectors explicitly so "it corrects the model" is not read as "delete it".
Per-plugin findings
guardrails (12 wired entries)
| hook |
mechanism |
class |
ablation? |
rationale |
| secret-pattern-detection |
deny-gate |
policy |
no |
12 credential regexes on write payloads; irreversible leak, model-independent |
| hardcoded-path-check |
deny-gate |
hybrid |
partial |
portability invariant, but only ever polices model sloppiness that review also catches |
| block-no-verify |
deny-gate |
policy |
no |
preserves the pre-commit/pre-push deterministic tier |
| block-dangerous-git |
deny-gate |
policy |
no |
irreversible-only block list (force-push, reset --hard, clean -f…) |
| block-hook-bypass |
deny-gate |
policy |
no |
keeps the Write/Edit gates non-optional (blocks Bash write routes) |
| flag-commit-pr-skill-bypass |
context-injection |
behavioral |
yes |
fixed prose nudge toward /pull-request create; no external truth consulted |
| block-noncanonical-commit |
deny-gate |
hybrid |
partial |
multi-line -m mangling is real, but the block at :769–789 denies ALL -m with no newline test — single-line git commit -m "fix: typo" blocked. Narrow to actual-newline and the residue is pure policy |
| block-convention-violation |
deny-gate |
policy |
no |
enforces the consumer repo's own declared subject pattern; opt-in |
| workflow-resilience-check |
context-injection |
behavioral |
yes |
two greps then a fixed ~120-word checklist; asserts nothing the model can't derive |
| cli-flag-verify |
context-injection |
hybrid |
no |
oracle = local binary's live --help; non-derivable ground truth |
| skill-reference-verify |
context-injection |
hybrid |
no |
oracle = live plugin tree state post-rename/delete |
| stale-path-verify |
context-injection |
hybrid |
no |
oracle = git-history disappearance; can't flag invented paths by construction |
Notes: two structurally separate PreToolUse groups share the identical Bash|PowerShell matcher (could be one); three scripts carry stale "Triggered on Bash" headers while wired Bash|PowerShell.
context-guard / autonomy / session-flow (6 entries)
| plugin |
hook |
mechanism |
class |
ablation? |
| context-guard |
zone-crossing-inject (PostToolBatch) |
context-injection (~1KB lecture + 4-option exit menu) |
behavioral |
yes — the #2009/I23 finding; payload hedges its own model-competence premise |
| context-guard |
zone-crossing-inject (UserPromptSubmit) |
context-injection |
behavioral |
yes — self-described redundancy cover for fresh-prompt turns |
| context-guard |
zone-gate |
deny-gate |
hybrid |
partial — deny predicate is "dumb context zone" model-competence guess; inert as shipped (advisory default); keep only the .compacted-marker branch |
| context-guard |
post-compact-mark |
infra transform |
policy |
no — records objective compaction fact; zero model-facing output |
| autonomy |
lane-stop-gate |
deny-gate + notify |
hybrid |
partial — block reason is a re-injected prompt admonition; trusted-config chain + operator alert are model-independent; default OFF |
| session-flow |
observer-arm |
infra |
policy |
no — out-of-band observer arming, anti-recursion guards; default OFF |
Default-posture reframe: three of the four gates are inert at default config; zone-crossing-inject is the only always-on surface.
claude-ops (8 entries)
All eight are pure-observability telemetry emitters (StopFailure, ConfigChange, InstructionsLoaded, PermissionDenied, PreCompact, Skill-usage x2, PostToolUseFailure): policy class, zero behavioral rows, zero ablation candidates on the model axis. Separate dead-weight axis (do not conflate): six of eight are currently inert because no HOOK_TELEMETRY_SINK is wired anywhere in the repo — they exit at hook::telemetry_enabled || exit 0. The two skill-usage producers still write skill-usage.jsonl unconditionally. hook-telemetry-sink.sh is unwired but is the documented consumer end, not an orphan.
source-control / rate-limit-guard / desktop-notification / disk-hygiene / project-local (9 entries)
| where |
hook |
mechanism |
class |
ablation? |
| source-control |
pr-body-linkage-gate |
deny-gate |
hybrid |
partial — self-described "POLICY gate, not a security guard"; motive is CI-round-trip latency; required CI check remains the authority; fails open on unreadable bodies |
| source-control |
pr-linkage-mcp-gate |
deny-gate |
hybrid |
partial — same split on the MCP surface |
| source-control |
worktree-create-gate |
transform/infra |
policy |
no — replaces creation to prevent nested-worktree rule-inheritance breakage; model not in the loop |
| rate-limit-guard |
record-rate-limit-stop |
infra (JSONL seam) |
neither |
n/a — StopFailure output is ignored by the harness; real question is whether anything still consumes stop-events.jsonl |
| desktop-notification |
desktop-notification |
notification |
neither |
n/a — output goes to the human, not the model |
| disk-hygiene |
destructive_guard.py (engine-gate) |
deny-gate |
policy |
no — enforces kill switch + data-root authority for the bundled engine only; hardens against harness fail-open (#1423) |
| disk-hygiene |
guard_launch_monitor.py |
context-injection |
neither (observability) |
no — detector for the PreToolUse fail-open shape (#1416); retire only if fixed upstream |
| project-local |
.claude/hooks/pr-linkage-mcp-gate |
deny-gate |
hybrid |
partial — distinct value is reach (cloud sessions with no plugin install); duplicate wiring note below |
| project-local |
.claude/hooks/session-start |
infra provisioning |
policy |
no — SHA-256-pinned installs, cloud-VM-only guard; genuine supply-chain gate |
Duplicate double-fire: with source-control@melodic-software enabled, .claude/settings.json wires the repo-local mcp-gate on the byte-identical matcher as the plugin's — both fire, both exit 2 with near-identical stderr on every MCP PR create/update. Ablating either leaves the surface covered.
Second registration: disk-hygiene's SKILL.md frontmatter wires the SAME destructive_guard.py with no --mode → strict belt posture; the hooks.json engine-gate row alone understates its footprint.
Formatter family (9 entries: actionlint, bash-format, biome-format, eol-normalizer, go-format, markdown-format, powershell-format, ruff-format, typos-format)
All PostToolUse Write|Edit, all exit 0 unconditionally, zero deny-gates against the model. Seven pure policy (config-gated deterministic transforms + external-tool ground-truth relays). Two hybrids: markdown-format (partial — ablatable surface is only the conduct-coaching strings, not the transform, the finding relay, or its fail-closed code-execution trust gate on .cjs/.mjs configs); typos-format (partial — report-only by default, so the wired surface is mostly injected wrapper prose; finding list is policy). powershell-format keeps: its CustomRulePath trust gate is a real consent invariant.
Cross-cutting: hook-utils.sh is one 72,723-byte file duplicated identically across 16 plugins (SSOT lib/hook-utils.sh, CI-checked); any shared-helper change hits all 16. Every plugin hook has a <name>_enabled kill switch — ablation can be tested by config-disable, no deletion required.
Proposed work (gated on the authority decision)
Acceptance criteria
References
Context
Menu item #5 of the Boris-talk digest (knowledge-corpus,
boris-cherny-we-cut-80-of-claude-code-s-qyPCVqFUyDo): hooks are officially carved out as the durable deterministic tier, BUT hooks that exist to correct model behavior (rather than enforce policy) are exactly what newer models obsolete. This is the classification pass that item called for — audit posture, report-only. Remediation is deliberately NOT included: the classification authority question (one-time audit PR vs standing playbook rubric — digestquestions.md#5) is an operator decision this issue exists to inform.Method: five parallel fresh-context read-only agents, one per plugin group, each reading
hooks/hooks.jsonwiring first and every wired non-test script in full. Classification axes: mechanism (deny-gate / context-injection / deterministic-transform / notification-infra) and class (policy = invariant you'd keep with a perfect model; behavioral = corrects model behavior a better model gets right unaided; hybrid = both, with the split named).Population note: the digest baseline said "108 hook scripts". Live count on main (
19a3f7b3): 18 hook-shipping plugins, 66 non-test.shhook scripts + 2 Python (disk-hygiene, wired fromskills/clean/scripts/) + 2 project-local.claude/hooksscripts; 126 files underplugins/*/hooks/including tests and sourced libs. The 108 figure appears to have counted test files; wired hook ENTRIES examined here: 44.Roll-up
zone-crossing-inject.sh(wired twice: PostToolBatch + UserPromptSubmit); guardrailsflag-commit-pr-skill-bypass.sh; guardrailsworkflow-resilience-check.shzone-gate.sh(predicate is model-competence; deny path inert by default); autonomylane-stop-gate.sh(lecture text behavioral, provenance hardening policy); guardrailshardcoded-path-check.sh; guardrailsblock-noncanonical-commit.sh(narrow to actual-newline-m); markdown-format (coaching strings only); typos-format (wrapper prose only); source-controlpr-body-linkage-gate.sh+pr-linkage-mcp-gate.sh(latency mirrors of a required CI check that remains the authority)Headline: in a default install, context-guard's
zone-crossing-inject.shis the ONLY always-on model-facing behavioral surface across the non-formatter plugins (autonomy's gate and the zone deny-path are default-off/advisory). It is also the subject of the already-filed #2009 (I23 flags its exit menu). Highest-value, lowest-risk ablation target; the two guardrails injectors (flag-commit-pr-skill-bypass,workflow-resilience-check) are the next two — static prose, no external oracle, kill-switchable without deletion.Key nuance the policy/behavioral split surfaced: several hooks have a behavioral purpose but a policy-grade oracle — guardrails'
cli-flag-verify(diffs written flags against the local binary's live--help),skill-reference-verify(globs the live plugin tree),stale-path-verify(git-history disappearance query). They correct hallucination, but with machine ground truth no model can know unaided. These are keeps; the report separates them from prose-only injectors explicitly so "it corrects the model" is not read as "delete it".Per-plugin findings
guardrails (12 wired entries)
/pull-request create; no external truth consulted-mmangling is real, but the block at :769–789 denies ALL-mwith no newline test — single-linegit commit -m "fix: typo"blocked. Narrow to actual-newline and the residue is pure policy--help; non-derivable ground truthNotes: two structurally separate PreToolUse groups share the identical
Bash|PowerShellmatcher (could be one); three scripts carry stale "Triggered on Bash" headers while wiredBash|PowerShell.context-guard / autonomy / session-flow (6 entries)
advisorydefault); keep only the.compacted-marker branchDefault-posture reframe: three of the four gates are inert at default config;
zone-crossing-injectis the only always-on surface.claude-ops (8 entries)
All eight are pure-observability telemetry emitters (StopFailure, ConfigChange, InstructionsLoaded, PermissionDenied, PreCompact, Skill-usage x2, PostToolUseFailure): policy class, zero behavioral rows, zero ablation candidates on the model axis. Separate dead-weight axis (do not conflate): six of eight are currently inert because no
HOOK_TELEMETRY_SINKis wired anywhere in the repo — they exit athook::telemetry_enabled || exit 0. The two skill-usage producers still writeskill-usage.jsonlunconditionally.hook-telemetry-sink.shis unwired but is the documented consumer end, not an orphan.source-control / rate-limit-guard / desktop-notification / disk-hygiene / project-local (9 entries)
stop-events.jsonlDuplicate double-fire: with
source-control@melodic-softwareenabled,.claude/settings.jsonwires the repo-local mcp-gate on the byte-identical matcher as the plugin's — both fire, bothexit 2with near-identical stderr on every MCP PR create/update. Ablating either leaves the surface covered.Second registration: disk-hygiene's
SKILL.mdfrontmatter wires the SAMEdestructive_guard.pywith no--mode→ strictbeltposture; the hooks.json engine-gate row alone understates its footprint.Formatter family (9 entries: actionlint, bash-format, biome-format, eol-normalizer, go-format, markdown-format, powershell-format, ruff-format, typos-format)
All PostToolUse
Write|Edit, allexit 0unconditionally, zero deny-gates against the model. Seven pure policy (config-gated deterministic transforms + external-tool ground-truth relays). Two hybrids: markdown-format (partial — ablatable surface is only the conduct-coaching strings, not the transform, the finding relay, or its fail-closed code-execution trust gate on.cjs/.mjsconfigs); typos-format (partial — report-only by default, so the wired surface is mostly injected wrapper prose; finding list is policy). powershell-format keeps: itsCustomRulePathtrust gate is a real consent invariant.Cross-cutting:
hook-utils.shis one 72,723-byte file duplicated identically across 16 plugins (SSOTlib/hook-utils.sh, CI-checked); any shared-helper change hits all 16. Every plugin hook has a<name>_enabledkill switch — ablation can be tested by config-disable, no deletion required.Proposed work (gated on the authority decision)
questions.mdfeat(hook-telemetry): marketplace-wide telemetry contract + markdown-formatter producer #5: one-time audit PR vs standing playbook rubric.zone-crossing-injectalready has context-guard: the zone-crossing injection hands the model an exit menu, and check I23 now flags it #2009); (2) narrowblock-noncanonical-committo actual-newline-m; (3) trim coaching strings in markdown-format/typos-format; (4) de-duplicate the double-fired MCP linkage gate; (5) decide the telemetry-sink question for claude-ops separately — it is dead-weight, not behavioral.Acceptance criteria
References
boris-cherny-we-cut-80-of-claude-code-s-qyPCVqFUyDo—recommendations/menu.mdfeat(hook-telemetry): marketplace-wide telemetry contract + markdown-formatter producer #5,recommendations/questions.mdfeat(hook-telemetry): marketplace-wide telemetry contract + markdown-formatter producer #5, RESEARCH.md RC2/RC8