Skip to content

fix(init): nested hook schema + honor CLAUDE_CONFIG_DIR#6

Open
DamieMoon wants to merge 1 commit into
GottZ:rootfrom
DamieMoon:fix/claude-hooks-nested-schema-and-config-dir
Open

fix(init): nested hook schema + honor CLAUDE_CONFIG_DIR#6
DamieMoon wants to merge 1 commit into
GottZ:rootfrom
DamieMoon:fix/claude-hooks-nested-schema-and-config-dir

Conversation

@DamieMoon
Copy link
Copy Markdown

Fixes #5.

ctx init reported Hooks ✓ while writing hooks that never execute. As verified in the issue, two independent defects combined (flat hook schema that current Claude Code silently ignores; hardcoded ~/.claude/settings.json that ignores CLAUDE_CONFIG_DIR), plus a non-idempotent hasHookEntry.

Changes

go/internal/cli/init.go

  • claudeSettingsPath() now honors CLAUDE_CONFIG_DIR: returns $CLAUDE_CONFIG_DIR/settings.json when set, else ~/.claude/settings.json (the Windows %USERPROFILE% branch is retained). Per your review, the originally-proposed ~/.config/claude-code fallback was dropped — the resolver is exactly CLAUDE_CONFIG_DIR-or-~/.claude. This also fixes the statusline step (shared resolver).
  • New newHookGroup(cmd) helper emits the nested matcher-group form {"hooks":[{"type":"command","command":cmd}]}; stepHooks() now writes that instead of the silently-ignored flat form.
  • hasHookEntry() recurses into a nested hooks[] (in addition to the legacy flat form), so re-running init is idempotent against both shapes and no longer appends duplicates.

Docs — nested the hook examples in README.md §3 and the brief.go --hook help. (The statusLine examples were already the correct shape and are left unchanged.)

Tests (go/internal/cli/init_test.go)

  • TestHasHookEntry keeps the legacy-flat cases and adds nested-form detection.
  • New TestNewHookGroup.
  • TestClaudeSettingsPath now covers both branches (unset → ~/.claude; set → $CLAUDE_CONFIG_DIR/settings.json).
  • Updated the TestLoadSettings_ExistingFile and TestSettingsSave_PreservesExisting fixtures to the nested form.

Verification

  • gofmt -l clean on the changed files; go vet ./... and go build ./... clean.
  • go test ./internal/cli/ passes, including the new/updated tests.

Note: the broader repo shows pre-existing gofmt drift under go1.26.0's gofmt (unrelated files). I left those untouched to keep this PR scoped to the fix.

🤖 Generated with Claude Code

`ctx init` reported `Hooks ✓` while writing hooks that never fire. Two
independent defects combined:

1. Flat hook schema — `stepHooks()` appended `{"type":"command","command":…}`
   directly into the event array. Current Claude Code requires the nested
   matcher-group form `{"hooks":[{"type":"command","command":…}]}` for every
   event (incl. matcher-less SubagentStart/SubagentStop) and silently ignores
   the flat form. Added `newHookGroup()` and emit it from `stepHooks()`.
2. Hardcoded path — `claudeSettingsPath()` always wrote `~/.claude/settings.json`
   and ignored `CLAUDE_CONFIG_DIR`, so on installs that relocate the config dir
   it wrote a file Claude Code never reads (this also defeated the statusline
   step via the shared resolver). Now: `$CLAUDE_CONFIG_DIR/settings.json` if
   set, else `~/.claude/settings.json`.

Also: `hasHookEntry()` now recurses into a nested `hooks[]` (in addition to the
legacy flat form), so re-running `init` is idempotent against both shapes and
no longer appends duplicates.

Docs/tests: nest the hook examples in README §3 and the `brief.go` help; update
`init_test.go` fixtures to the nested form and add coverage for
`CLAUDE_CONFIG_DIR` resolution and nested-entry detection.

Per maintainer review, the originally-proposed `~/.config/claude-code`
fallback was dropped — the resolver is exactly CLAUDE_CONFIG_DIR-or-~/.claude.

gofmt/vet/build clean; `internal/cli` package tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <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.

ctx init installs non-functional Claude Code hooks: flat schema (silently ignored) + ignores CLAUDE_CONFIG_DIR

1 participant