Skip to content

fix(doctor): detect new Claude Code hook exec-form schema#177

Merged
kengio merged 4 commits into
mainfrom
fix/doctor-hook-schema-check
May 12, 2026
Merged

fix(doctor): detect new Claude Code hook exec-form schema#177
kengio merged 4 commits into
mainfrom
fix/doctor-hook-schema-check

Conversation

@kengio
Copy link
Copy Markdown
Collaborator

@kengio kengio commented May 12, 2026

Summary

/doctor was false-flagging both required hooks as missing on vaults whose register-hooks had already migrated entries to the canonical exec-form schema ({command: "onebrain", args: ["checkpoint", "stop"]}). The substring check only inspected the command field, which under the new schema is just "onebrain" — the verb lives in args[].

This PR teaches the validator to compute an effective command string by joining command + args[] before substring matching, so both legacy and exec form resolve to "onebrain checkpoint stop". It also adds form classification so legacy entries are surfaced (not hidden) and the existing --fix migration path can take over.

Changes

  • validator.ts: new effectiveCommand() + detectHookForm() helpers; required-hook check now distinguishes exec / legacy / absent
  • Stale-hook sweep uses the same joined string — catches onebrain references buried in args[]
  • --fix path unchanged: runRegisterHooks already migrates legacy → exec via its rewriteIfShellForm pass
  • 6 new unit tests in src/lib/index.test.ts covering exec, legacy, bash-wrapper, absent, mixed-group, and qmd-skip
  • doctor/SKILL.md description updated to match new behavior
  • CLI 2.3.1 → 2.3.2 · plugin 2.4.10 → 2.4.11

Test plan

  • bun run typecheck clean
  • bun test src/lib/index.test.ts — 27/27 pass (6 new + 21 existing)
  • bun test src/commands/internal/register-hooks.test.ts — 40/40 pass
  • Full suite delta vs main: same 13 pre-existing TTY-timeout flakes, +6 pass from new tests
  • Manually confirmed user's current vault (canonical exec form) now passes hook check

kengio added 4 commits May 12, 2026 17:18
CLI 2.3.1 → 2.3.2 · plugin 2.4.10 → 2.4.11

`checkSettingsHooks` was matching the legacy shell form only — looking
for `"onebrain checkpoint stop"` inside the `command` field. Claude
Code's current hook schema splits this into `{command: "onebrain",
args: ["checkpoint", "stop"]}`, so the substring check missed every
canonical entry and `/doctor` false-flagged both required hooks as
missing on vaults whose `register-hooks` had already migrated them.

Changes:
- New `effectiveCommand()` joins `command + args[]` so a single
  substring check handles both schemas
- New `detectHookForm()` returns `exec` / `legacy` / `absent` —
  legacy entries now warn "--fix will migrate to exec form" instead
  of being invisible
- Stale-hook sweep also uses the joined effective command so a stale
  reference hidden inside `args[]` is no longer missed
- `--fix` path unchanged: `runRegisterHooks` already migrates legacy
  shell-form to exec form via its existing `rewriteIfShellForm` pass
- 6 new unit tests cover canonical exec, legacy shell, bash-wrapper,
  absent, mixed-group, and qmd-skip cases
- doctor SKILL.md updated to describe the effective-command rule
- detectHookForm now scans all matching entries per event and reports
  exec if any matching entry is canonical, only falling back to legacy
  when no canonical exists. Handles partial-migration state where a
  stale legacy entry was left behind alongside a new canonical one
- effectiveCommand filters non-string args entries — hand-edited
  settings.json with stray null/numbers can't produce ghost matches
- 4 new tests: partial-migration duplicates, mixed Stop+PostToolUse
  migration, stale exec-form event detection, defensive args filter
@kengio kengio merged commit c50160b into main May 12, 2026
1 check passed
@kengio kengio deleted the fix/doctor-hook-schema-check branch May 12, 2026 11:08
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