Skip to content

docs: reconcile kill-switch doctrine — per-hook toggles are userConfig - #321

Merged
kyle-sexton merged 2 commits into
mainfrom
docs/kill-switch-doctrine
Jul 18, 2026
Merged

docs: reconcile kill-switch doctrine — per-hook toggles are userConfig#321
kyle-sexton merged 2 commits into
mainfrom
docs/kill-switch-doctrine

Conversation

@kyle-sexton

@kyle-sexton kyle-sexton commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Resolves doctrine finding 1 of epic #313 (the finding, not the epic). No linked issue closes with this PR.

What

The philosophy doc mandated migrating personal env toggles to userConfig (env channel retires), while the migration playbook endorsed HOOK_<PLUGIN>_ENABLED as the ecosystem norm and its plugin-acceptance security review required that env var to exist. This PR reconciles: migration wins, with a scope carve-out.

The reconciliation line (added to PLUGIN-PHILOSOPHY § Configuration ownership and scope): a hook kill switch is a personal scalar — per-hook selectivity ships as a userConfig boolean with default: true, read through the hook-process CLAUDE_PLUGIN_OPTION_<KEY> mirror. Per-project control stays whole-plugin via scope-level enabledPlugins; a genuinely project-scoped per-hook behavior graduates to the tracked consumer-project file on demonstrated need — never a custom env channel.

Playbook sites updated to match

  • Bundling rationale ('Hooks group by concern' + 'Why capability, not grab-bag'): per-hook control now cites the userConfig toggle, not the env kill-switch.
  • Security review item 1: the kill-switch existence check now requires a per-hook userConfig boolean (default true).
  • Reintegration seam map: toggles route through /plugin configure / claude plugin install --config; per-repo control is the enabledPlugins entry; the no-seam plugin-gap escape now names a tracked consumer-project config key instead of a new env var.
  • Cutover checklist step 2: set non-default userConfig toggles instead of rewiring an env var.

Deliberately unchanged: the consumer-side HOOK_TELEMETRY_SINK seam (project-scoped observability wiring, not a personal toggle — classified in the flagship migration), legacy in-repo hook toggles in the cutover procedure, and docs/hook-migration-audit.md (a historical audit record).

Verification

  • Facts re-verified this session against https://code.claude.com/docs/en/plugins-reference and https://code.claude.com/docs/en/hooks: all userConfig values export to hook processes as CLAUDE_PLUGIN_OPTION_<KEY>; pluginConfigs is read from user settings, --settings, and managed settings only (v2.1.207+); no native per-hook disable exists (only disableAllHooks, if, or whole-plugin disable — enabledPlugins is scope-level).
  • markdownlint-cli2: 0 errors on both files.
  • Grep confirms no stale HOOK_*_ENABLED doctrine text remains in either doctrine doc.

Unblocks the #315 plugin migrations (guardrails flagship + 9 single-switch hook plugins + knowledge/source-control scalars).

🤖 Generated with Claude Code

Related

…g, env channel retires

Resolves doctrine finding 1 of the fleet conformance audit (#313): the
philosophy mandated env-toggle-to-userConfig migration while the playbook
endorsed HOOK_<PLUGIN>_ENABLED as the ecosystem norm and its security review
required that env var to exist.

Ruling: migration wins. Per-hook selectivity ships as a userConfig boolean
(default true) read through the hook-process CLAUDE_PLUGIN_OPTION_<KEY>
mirror; per-project control stays whole-plugin via scope-level
enabledPlugins; a genuinely project-scoped per-hook behavior graduates to
the tracked consumer-project file on demonstrated need — never a custom env
channel. Playbook bundling rationale, security-review check, reintegration
seam map, and cutover checklist updated to match. The consumer-side
HOOK_TELEMETRY_SINK seam and legacy in-repo hook toggles are unchanged.

Unblocks the #315 userConfig wave (facts re-verified against the plugins
reference and hooks pages this session; pluginConfigs is user/--settings/
managed scope only, v2.1.207+).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@kyle-sexton
kyle-sexton enabled auto-merge (squash) July 18, 2026 01:35

@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: de5bee5cde

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

Comment thread docs/MIGRATION-PLAYBOOK.md Outdated
Codex review: --config applies only on a fresh install and is ignored once
the plugin is installed (smoke-test C), so the checklist now seeds toggles
in step 1's install command and reserves step 2 for interactive
/plugin configure; headless reconfiguration = uninstall/reinstall.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
kyle-sexton added a commit that referenced this pull request Jul 18, 2026
…plugins) (#325)

Part of #315 (userConfig wave). No linked issue closes with this PR —
closing #315 is gated on the hook-channel PR #323 landing and the wave
acceptance sweep.

## What

Completes the #315 scalar migrations outside the shared hook-utils wave
(#323). These scalars are read by skill-invoked scripts or skill
content, where the `CLAUDE_PLUGIN_OPTION_*` mirror does not reach — each
value now flows through non-sensitive `${user_config.*}` substitution in
the skill body into an explicit script flag, or (for the two
skill-scoped guard hooks, which ARE hook processes) through the mirror
directly.

## Per plugin

- **source-control 0.7.0** — `worktree_stale_days` (default 14),
`babysit_self_logins` (csv, default empty), `fetch_logs_max_bytes`
(default 52428800). `babysit-readiness-gate.sh` gains an additive
`--extra-self` flag — preserving the retired env var's
add-to-personal-login semantics; the pre-existing `--self` full-override
contract is unchanged. `fetch-failed-logs.sh` gains `--max-bytes`. Skill
bodies carry the substituted values (reference/context files are not
substituted, so the values enter context via SKILL.md); every
instruction includes the unexpanded-token fallback.
- **knowledge 0.7.0** — `yt_dlp_js_runtimes` (default "node"),
`yt_dlp_cookies_file`, `yt_dlp_cookies_from_browser`,
`max_concurrent_acquires` (default 1, max 3), wired through new
`run.mjs` launcher flags on the plugin's existing `library_dir` →
`--work-root` precedent; the `YOUTUBE_*` env vars remain only as the
launcher-to-child internal interface. Course-platform credentials
(`TEACHABLE_*`/`COURSE_*`) deliberately stay in shell env — `sensitive:
true` persists as plaintext in `~/.claude/.credentials.json` on Windows
— and that misfit is now documented at the site (the audit's dim-6
requirement).
- **disk-hygiene 0.3.0** — `disk_hygiene_enabled` (default true) read by
the skill-scoped guard via `CLAUDE_PLUGIN_OPTION_DISK_HYGIENE_ENABLED`.
- **repo-hygiene 0.2.0** — `clean_destructive_guard_enabled` (default
true) via `CLAUDE_PLUGIN_OPTION_CLEAN_DESTRUCTIVE_GUARD_ENABLED`.
- **planning** — the audit's `use_ask_user_question` description-copy
defect no longer reproduces (fixed upstream before this wave); no change
shipped.

**BREAKING (consumer-visible):** `WORKTREE_STALE_DAYS`,
`BABYSIT_SELF_LOGINS`, `FETCH_LOGS_MAX_BYTES`,
`HOOK_DISK_HYGIENE_ENABLED`, `HOOK_CLEAN_DESTRUCTIVE_GUARD_ENABLED`, and
the four `YOUTUBE_*` consumer channels are no longer read from
consumer-set env; re-express non-default values as the matching
userConfig options. Zero-config behavior unchanged everywhere.
`FETCH_LOGS_SCRATCH` / `FETCH_LOGS_REPO` remain env (out of #315 scope).

## Verification

- babysit-readiness-gate suite 47 PASS (including the new `--extra-self`
path); repo-hygiene guard suite 30 PASS; disk-hygiene pytest OK;
knowledge vitest 33/33 on the four affected files + `tsc --noEmit`
clean.
- fetch-failed-logs suite requires `zip` (absent on this machine — it
self-skips; CI runs it). The `--max-bytes` change is a one-line
default-substitution swap plus a validated flag parse.
- `scripts/validate-plugins.sh` (manifests + catalog, strict) and `node
scripts/generate-catalog.mjs --check` green; markdownlint 0 errors on
every edited file; residual grep confirms no consumer-facing doc still
instructs setting a retired env var.
- knowledge `package-lock.json` verified pristine (a subagent's local
`npm install` was reverted before commit).

## Related

- #313 (fleet conformance audit epic — dims 3+6 remediation)
- #315 (userConfig wave — this PR completes the skill-channel portion)
- #321 (kill-switch doctrine reconciliation)
- #323 (hook-channel userConfig wave)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
@kyle-sexton
kyle-sexton merged commit f250a95 into main Jul 18, 2026
19 of 20 checks passed
@kyle-sexton
kyle-sexton deleted the docs/kill-switch-doctrine branch July 18, 2026 02:48
kyle-sexton added a commit that referenced this pull request Jul 18, 2026
)

Part of #315 (userConfig wave). No linked issue closes with this PR —
#315 stays open for the remaining skill-channel scalar migrations
(knowledge, source-control, disk-hygiene, repo-hygiene, planning
description fix).

## What

Applies the kill-switch doctrine ruling (PR #321) across every plugin
carrying the shared `lib/hook-utils.sh`: per-hook selectivity is now a
`userConfig` boolean (default `true`) read through the hook-process
`CLAUDE_PLUGIN_OPTION_<KEY>` mirror, and the custom `HOOK_*` env channel
retires in the same release.

**Scope note:** #315 listed 9 same-pattern plugins; the shared-library
reader change atomically flips every plugin carrying a synced copy,
which adds `biome-format` and `claude-ops` (both call
`hook::check_enabled`) to this wave — 10 plugins total. `disk-hygiene`
and `repo-hygiene` (listed in #315) carry no hook-utils copy; their
switches live in skill-invoked scripts where the mirror does not reach,
so they move with the skill-channel PR.

## Per plugin

| Plugin | Version | Options added |
|---|---|---|
| guardrails (flagship) | 0.5.0 | 7 per-guard `*_enabled` toggles,
`cli_flag_verify_bins`, `cli_flag_verify_skip_bins`,
`stdin_read_timeout` |
| claude-ops | 0.11.0 | 7 audit-hook toggles,
`instructions_loaded_audit_log_session_start`, `stdin_read_timeout`
(existing `registry_dir`/`skill_usage_dir`/`install_new` preserved) |
| desktop-notification | 0.2.0 | master toggle + 3 per-channel mutes,
`stdin_read_timeout` |
| actionlint 0.2.0 · bash-format 0.3.0 · biome-format 0.2.0 ·
eol-normalizer 0.2.0 · markdown-format 0.3.0 · powershell-format 0.2.0 ·
ruff-format 0.2.0 | — | single `<plugin>_enabled` toggle +
`stdin_read_timeout` each |

Every plugin: README configuration prose rewritten (`/plugin configure`,
`claude plugin install --config`, user-scope note, per-repo disable via
`enabledPlugins`) and a CHANGELOG entry with the BREAKING env-retirement
note.

**BREAKING (consumer-visible):** `HOOK_<NAME>_ENABLED`,
`HOOK_STDIN_READ_TIMEOUT`, `HOOK_CLI_FLAG_VERIFY_BINS`,
`HOOK_CLI_FLAG_VERIFY_SKIP_BINS`,
`HOOK_INSTRUCTIONS_LOADED_AUDIT_LOG_SESSION_START` are no longer read;
re-express any non-default value as the matching userConfig option.
Zero-config behavior unchanged. The consumer-side `HOOK_TELEMETRY_SINK`
seam is untouched.

## Verification

- Boolean mirror serialization verified against the installed CC 2.1.214
bundle: options export as `String(value)` → `"true"`/`"false"`, so the
enabled-unless-"false" reader semantics hold; the reader's
`${!var:-true}` fallback also covers an absent mirror.
- Full plugin test suite green (`scripts/run-plugin-tests.sh` exit 0;
guardrails 248 + lib 46 assertions pass).
- `scripts/sync-hook-utils.sh --check`,
`scripts/check-cross-plugin-source-drift.sh --check`,
`scripts/validate-plugins.sh` (manifests + catalog, strict), `node
scripts/generate-catalog.mjs --check`: all green.
- markdownlint clean on every edited markdown file.
- One sed collateral caught and repaired: a `LEFTHOOK_*` lefthook-prefix
test fixture in block-no-verify.test.sh.

## Related

- #313 (fleet conformance audit epic — dims 3+6 remediation)
- #315 (userConfig wave — this PR covers the hook-channel portion)
- #321 (kill-switch doctrine reconciliation this PR implements)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
kyle-sexton added a commit that referenced this pull request Jul 18, 2026
Reconcile the block-dangerous-git guard with the kill-switch doctrine
main adopted while this PR was in review (#321/#323): per-hook toggles
and tuning scalars are native userConfig, not HOOK_* env vars.

- lib/hook-utils.sh: take main's userConfig check_enabled/buffer_stdin
  and shfmt formatting; keep this branch's argv-grammar parser.
- guardrails plugin.json: bump 0.5.0 -> 0.6.0 (main's 0.5.0 was the
  userConfig migration); add block_dangerous_git_enabled (boolean) and
  block_dangerous_git_allow (string list) userConfig options; correct
  the guard count Seven -> Eight.
- block-dangerous-git.sh/.test.sh: kill switch flows through
  hook::check_enabled (CLAUDE_PLUGIN_OPTION mirror); allowed() reads
  CLAUDE_PLUGIN_OPTION_BLOCK_DANGEROUS_GIT_ALLOW; docs/messages/tests
  migrated off HOOK_BLOCK_DANGEROUS_GIT_*.
- block-no-verify.sh: keep this branch's shared-parser refactor; take
  main's userConfig bypass message.
- README/CHANGELOG: merge main's userConfig tables/entries with this
  guard's; new guard entry is [0.6.0] above main's [0.5.0].
- Nine format-plugin versions resolved to main's + patch (check-bump
  gate: lib changed vs main).

Contract suites 181 + 75 green, shellcheck clean, check-bump and
hook-utils drift checks pass.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.

1 participant