Skip to content

Commit 3816fce

Browse files
shreyas-lyzrclaude
andcommitted
fix: generate correct Claude Code hooks JSON structure (#36)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2 parents 173eac1 + ebbfecb commit 3816fce

File tree

1 file changed

+27
-10
lines changed

1 file changed

+27
-10
lines changed

src/runners/claude.ts

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,15 @@ function collectExtraDirs(agentDir: string): string[] {
190190

191191
/**
192192
* Map hooks/hooks.yaml to Claude Code settings format.
193-
* Claude Code hooks: { hooks: { "<event>": [{ command, ...}] } }
193+
*
194+
* Claude Code expects:
195+
* {
196+
* hooks: {
197+
* "<event>": [
198+
* { matcher: "<pattern>", hooks: [{ type: "command", command: "..." }] }
199+
* ]
200+
* }
201+
* }
194202
*/
195203
function buildHooksSettings(agentDir: string): string | null {
196204
const hooksPath = join(agentDir, 'hooks', 'hooks.yaml');
@@ -210,16 +218,16 @@ function buildHooksSettings(agentDir: string): string | null {
210218

211219
// Map gitagent hook events to Claude Code hook events
212220
const eventMap: Record<string, string> = {
213-
'on_session_start': 'PreToolUse',
221+
'on_session_start': 'SessionStart',
214222
'pre_tool_use': 'PreToolUse',
215223
'post_tool_use': 'PostToolUse',
216-
'pre_response': 'PostToolUse',
217-
'post_response': 'PostToolUse',
218-
'on_error': 'PostToolUse',
219-
'on_session_end': 'PostToolUse',
224+
'pre_response': 'UserPromptSubmit',
225+
'post_response': 'Stop',
226+
'on_error': 'PostToolUseFailure',
227+
'on_session_end': 'SessionEnd',
220228
};
221229

222-
const ccHooks: Record<string, Array<{ type: string; command: string }>> = {};
230+
const ccHooks: Record<string, Array<{ matcher: string; hooks: Array<{ type: string; command: string }> }>> = {};
223231

224232
for (const [event, hooks] of Object.entries(hooksConfig.hooks)) {
225233
const ccEvent = eventMap[event];
@@ -229,24 +237,33 @@ function buildHooksSettings(agentDir: string): string | null {
229237
ccHooks[ccEvent] = [];
230238
}
231239

240+
const hookCommands: Array<{ type: string; command: string }> = [];
232241
for (const hook of hooks) {
233242
const scriptPath = join(agentDir, 'hooks', hook.script);
234243
if (existsSync(scriptPath)) {
235-
ccHooks[ccEvent].push({
244+
hookCommands.push({
236245
type: 'command',
237246
command: `bash ${scriptPath}`,
238247
});
239248
}
240249
}
250+
251+
if (hookCommands.length > 0) {
252+
ccHooks[ccEvent].push({
253+
matcher: '',
254+
hooks: hookCommands,
255+
});
256+
}
241257
}
242258

243259
if (Object.keys(ccHooks).length === 0) return null;
244260

245261
const settings = { hooks: ccHooks };
246262
const tmpFile = join(tmpdir(), `gitagent-hooks-${randomBytes(4).toString('hex')}.json`);
247-
writeFileSync(tmpFile, JSON.stringify(settings), 'utf-8');
263+
writeFileSync(tmpFile, JSON.stringify(settings, null, 2), 'utf-8');
248264

249-
warn(`Mapped ${Object.values(ccHooks).flat().length} hooks to Claude Code settings`);
265+
const totalHooks = Object.values(ccHooks).reduce((sum, entries) => sum + entries.reduce((s, e) => s + e.hooks.length, 0), 0);
266+
warn(`Mapped ${totalHooks} hooks to Claude Code settings`);
250267
return tmpFile;
251268
} catch {
252269
return null;

0 commit comments

Comments
 (0)