Skip to content

Commit 0a0f4b3

Browse files
committed
fix: use correct Claude Code hook schema keys
Changed JSON keys from "PreToolUseHooks"/"SessionEndHooks" to "PreToolUse"/"SessionEnd" to match Claude Code's settings schema. The incorrect keys caused Claude Code to reject the generated settings.local.json with "Invalid key in record" errors. Signed-off-by: Jose Alekhinne <alekhinejose@gmail.com>
1 parent ffcdd16 commit 0a0f4b3

5 files changed

Lines changed: 24 additions & 23 deletions

File tree

.context/TASKS.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
#started:2026-01-26-034500 #done:2026-01-26-035700
2323
- [x] The LLM should also be proactive in saving decision when it makes
2424
sense; and suggesting new tasks. #started:2026-01-26-034500 #done:2026-01-26-035700
25-
- [ ]: Bug in hook creation:
25+
- [x] Bug in hook creation:
2626
```text
2727
/home/volkan/WORKSPACE/spike/.claude/settings.local.json
2828
└ hooks
@@ -32,6 +32,7 @@
3232
1. the "Hooks" suffix is not in the schema;
3333
2. the generated settings.local.json had unicode characters that broke the interpretation.
3434
make sure the file is properly-generated, and it works as expected.
35+
#started:2026-01-26-051500 #done:2026-01-26-052200
3536
- [ ]: `/ctx-save` slash command triggers approval prompt despite using ```` ```! ````
3637
auto-execute syntax. The command should run without requiring manual approval.
3738

internal/claude/embed.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,8 @@ func GetCommand(name string) ([]byte, error) {
8282
// CreateDefaultHooks returns the default ctx hooks configuration for
8383
// Claude Code.
8484
//
85-
// The returned hooks configure PreToolUseHooks to block non-PATH ctx
86-
// invocations and auto-load context on every tool use, and SessionEndHooks
85+
// The returned hooks configure PreToolUse to block non-PATH ctx
86+
// invocations and auto-load context on every tool use, and SessionEnd
8787
// to run auto-save-session.sh for persisting session transcripts.
8888
//
8989
// Parameters:
@@ -99,7 +99,7 @@ func CreateDefaultHooks(projectDir string) HookConfig {
9999
}
100100

101101
return HookConfig{
102-
PreToolUseHooks: []HookMatcher{
102+
PreToolUse: []HookMatcher{
103103
{
104104
// Block non-PATH ctx invocations (./ctx, ./dist/ctx, go run ./cmd/ctx)
105105
Matcher: "Bash",
@@ -121,7 +121,7 @@ func CreateDefaultHooks(projectDir string) HookConfig {
121121
},
122122
},
123123
},
124-
SessionEndHooks: []HookMatcher{
124+
SessionEnd: []HookMatcher{
125125
{
126126
Hooks: []Hook{
127127
{

internal/claude/embed_test.go

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -109,20 +109,20 @@ func TestCreateDefaultHooks(t *testing.T) {
109109
t.Run(tt.name, func(t *testing.T) {
110110
hooks := CreateDefaultHooks(tt.projectDir)
111111

112-
// Check PreToolUseHooks hooks
113-
if len(hooks.PreToolUseHooks) == 0 {
114-
t.Error("CreateDefaultHooks() PreToolUseHooks is empty")
112+
// Check PreToolUse hooks
113+
if len(hooks.PreToolUse) == 0 {
114+
t.Error("CreateDefaultHooks() PreToolUse is empty")
115115
}
116116

117-
// Check SessionEndHooks hooks
118-
if len(hooks.SessionEndHooks) == 0 {
119-
t.Error("CreateDefaultHooks() SessionEndHooks is empty")
117+
// Check SessionEnd hooks
118+
if len(hooks.SessionEnd) == 0 {
119+
t.Error("CreateDefaultHooks() SessionEnd is empty")
120120
}
121121

122122
// Check that project dir is used in paths when provided
123123
if tt.projectDir != "" {
124124
found := false
125-
for _, matcher := range hooks.PreToolUseHooks {
125+
for _, matcher := range hooks.PreToolUse {
126126
for _, hook := range matcher.Hooks {
127127
if strings.Contains(hook.Command, tt.projectDir) {
128128
found = true
@@ -147,8 +147,8 @@ func TestSettingsStructure(t *testing.T) {
147147
},
148148
}
149149

150-
if len(settings.Hooks.PreToolUseHooks) == 0 {
151-
t.Error("Settings.Hooks.PreToolUseHooks should not be empty")
150+
if len(settings.Hooks.PreToolUse) == 0 {
151+
t.Error("Settings.Hooks.PreToolUse should not be empty")
152152
}
153153

154154
if settings.Permissions == nil {

internal/claude/types.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,16 @@ package claude
1313
// events. See https://docs.anthropic.com/en/docs/claude-code/hooks for details.
1414
//
1515
// Fields:
16-
// - PreToolUseHooks: Matchers that run before each tool invocation
17-
// - SessionEndHooks: Matchers that run when a session ends
16+
// - PreToolUse: Matchers that run before each tool invocation
17+
// - SessionEnd: Matchers that run when a session ends
1818
type HookConfig struct {
19-
PreToolUseHooks []HookMatcher `json:"PreToolUseHooks,omitempty"`
20-
SessionEndHooks []HookMatcher `json:"SessionEndHooks,omitempty"`
19+
PreToolUse []HookMatcher `json:"PreToolUse,omitempty"`
20+
SessionEnd []HookMatcher `json:"SessionEnd,omitempty"`
2121
}
2222

2323
// HookMatcher associates a regex pattern with hooks to execute.
2424
//
25-
// For PreToolUseHooks, the Matcher pattern matches against the tool name
25+
// For PreToolUse hooks, the Matcher pattern matches against the tool name
2626
// (e.g., "Bash", "Read"). Use ".*" to match all tools.
2727
//
2828
// Fields:

internal/cli/initialize/hook.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,8 @@ func mergeSettingsHooks(
130130
defaultHooks := claude.CreateDefaultHooks(projectDir)
131131

132132
// Check if hooks already exist
133-
hasPreToolUse := len(settings.Hooks.PreToolUseHooks) > 0
134-
hasSessionEnd := len(settings.Hooks.SessionEndHooks) > 0
133+
hasPreToolUse := len(settings.Hooks.PreToolUse) > 0
134+
hasSessionEnd := len(settings.Hooks.SessionEnd) > 0
135135

136136
if fileExists && hasPreToolUse && hasSessionEnd && !force {
137137
cmd.Printf(
@@ -143,11 +143,11 @@ func mergeSettingsHooks(
143143
// Merge hooks - only add what's missing
144144
modified := false
145145
if !hasPreToolUse || force {
146-
settings.Hooks.PreToolUseHooks = defaultHooks.PreToolUseHooks
146+
settings.Hooks.PreToolUse = defaultHooks.PreToolUse
147147
modified = true
148148
}
149149
if !hasSessionEnd || force {
150-
settings.Hooks.SessionEndHooks = defaultHooks.SessionEndHooks
150+
settings.Hooks.SessionEnd = defaultHooks.SessionEnd
151151
modified = true
152152
}
153153

0 commit comments

Comments
 (0)