You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Proposal: codify a "delivering a userConfig value to a hook" channel decision matrix (convention)
Multiple plugins deliver a userConfig scalar into a hook's decision logic, and they do it three
different ways with no documented rule — the exact reuse-or-replace fragmentation:
This proposes a single decision matrix, grounded in verified Claude Code behavior (2.1.218), so every
plugin/fix picks a channel by rule instead of ad hoc. Driving cases: the two inbox findings below.
Verified channel facts (CC 2.1.218; citations)
Plugin hooks.json hooks get ${user_config.KEY} (exec form only) andCLAUDE_PLUGIN_OPTION_<KEY> env — plugins-reference (userConfig section); shell-form ${user_config.*} is rejected since CC 2.1.207 (use exec args or the env var).
A declared default is in the schema but NOT implemented for substitution — an unset-but-defaulted ${user_config.*} resolves as unset. Upstream: #46477 (closed not planned) + comments (independent repro on an MCP url; "its in the schema… just not implemented"); #39455 / #39827. No CHANGELOG entry ever implemented it (latest 2.1.218).
Skill/agent body${user_config.KEY} substitutes into model-visible content, non-sensitive only — plugins-reference.
Open (undocumented): whether a project/local settings.jsonenv block overrides the harness-injected CLAUDE_PLUGIN_OPTION_* (decides whether the env channel is repo-tamper-resistant). Inference leans "injection wins → safe," unverified.
Plugin hook, MANDATORY value (no sensible default) → D: required:true + argv. Trusted, simplest, sidesteps Fact 3 (no unset case).
Plugin hook, optional-with-default, safety/security-critical → never bare argv (Fact 3 drops it). Baseline B (env + in-script default). Add C only if a configured value must resist a hostile repo AND Fact 5 resolves against B.
Sensitive value → B or C, never E or logged argv.
Driving cases (inbox findings, filed separately)
Item 1 — disk-hygiene unconfigured kill switch: plugin hook on a safety+optional-default value uses bare argv → violates rule 3; Fact 3 drops the hook → the engine-gate/kill-switch is absent on unconfigured installs (observed; cause inferred). Fix = apply rule 3 + raise Fact 3 upstream (root cause).
Item 3 — PowerShell-tool guards inert: the PowerShell tool is a documented preview tool (tools-reference), matcher name PowerShell is doc-sanctioned, and hook-firing is not a listed preview limitation — yet a Bash|PowerShell PreToolUse matcher does not intercept PowerShell-tool commands on 2.1.218 (reproduced). So the matcher is doc-correct (not a plugin fix) → this is a docs-vs-behavior divergence to raise upstream + document the limitation. Ecosystem-wide (guardrails + disk-hygiene). Windows hook-payload lineage: #57137, #36156, #16564 (all closed).
Ask
Adopt the matrix + rule as a convention doc (where — docs/? a shared plugin note?).
Confirm the rule-3 default (B baseline; C only when tamper-resistance is required) — or resolve Fact 5 first.
Track the two upstream raises (Fact 3; the PowerShell-tool hook divergence).
Full research + evidence table + confidence/gaps: session artifact RESEARCH.md (not committed).
Proposal: codify a "delivering a userConfig value to a hook" channel decision matrix (convention)
Multiple plugins deliver a
userConfigscalar into a hook's decision logic, and they do it threedifferent ways with no documented rule — the exact reuse-or-replace fragmentation:
hooks.json): exec-form argv${user_config.disk_hygiene_enabled}— broken (see Fact 3).CLAUDE_PLUGIN_OPTION_*env — but skill hooks don't get it (Fact 2), so always defaults enabled (disk-hygiene: kill switch (disk_hygiene_enabled) cannot reach a skill-frontmatter guard hook — audit-only degrades to prompt-gated #1019).${CLAUDE_PLUGIN_DATA}file channel — unbuilt.This proposes a single decision matrix, grounded in verified Claude Code behavior (2.1.218), so every
plugin/fix picks a channel by rule instead of ad hoc. Driving cases: the two inbox findings below.
Verified channel facts (CC 2.1.218; citations)
hooks.jsonhooks get${user_config.KEY}(exec form only) andCLAUDE_PLUGIN_OPTION_<KEY>env — plugins-reference (userConfig section); shell-form${user_config.*}is rejected since CC 2.1.207 (use execargsor the env var).defaultis in the schema but NOT implemented for substitution — an unset-but-defaulted${user_config.*}resolves as unset. Upstream: #46477 (closed not planned) + comments (independent repro on an MCPurl; "its in the schema… just not implemented"); #39455 / #39827. No CHANGELOG entry ever implemented it (latest 2.1.218).${user_config.KEY}substitutes into model-visible content, non-sensitive only — plugins-reference.settings.jsonenvblock overrides the harness-injectedCLAUDE_PLUGIN_OPTION_*(decides whether the env channel is repo-tamper-resistant). Inference leans "injection wins → safe," unverified.The matrix
${user_config.KEY}CLAUDE_PLUGIN_OPTION_<KEY>${CLAUDE_PLUGIN_DATA}filerequired:true+ argv (no default)${user_config.KEY}The decision rule
required:true+ argv. Trusted, simplest, sidesteps Fact 3 (no unset case).Driving cases (inbox findings, filed separately)
PowerShelltool is a documented preview tool (tools-reference), matcher namePowerShellis doc-sanctioned, and hook-firing is not a listed preview limitation — yet aBash|PowerShellPreToolUse matcher does not intercept PowerShell-tool commands on 2.1.218 (reproduced). So the matcher is doc-correct (not a plugin fix) → this is a docs-vs-behavior divergence to raise upstream + document the limitation. Ecosystem-wide (guardrails + disk-hygiene). Windows hook-payload lineage: #57137, #36156, #16564 (all closed).Ask
docs/? a shared plugin note?).Full research + evidence table + confidence/gaps: session artifact
RESEARCH.md(not committed).