Skip to content

Commit 3960870

Browse files
cm-dyoshikawaclaude
andcommitted
feat(claudecode): support the current documented hook events
Claude Code documents 30 hook events but rulesync's claudecode hook map covered only 14. Add the 16 missing events — instructionsLoaded, userPromptExpansion, postToolUseFailure, postToolBatch, permissionDenied, subagentStart, taskCreated, taskCompleted, stopFailure, teammateIdle, configChange, cwdChanged, fileChanged, postCompact, elicitation, elicitationResult — to the canonical HookEvent union (12 new), the CANONICAL_TO_CLAUDE_EVENT_NAMES PascalCase mapping, and CLAUDE_HOOK_EVENTS, so `generate -t claudecode -f hooks` emits them. Verified each event against the official docs. Scoped to events only (handler fields are out of scope per the issue). Updates the hook event x tool matrix in file-formats.md. Closes #1628 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent dea2455 commit 3960870

4 files changed

Lines changed: 147 additions & 8 deletions

File tree

docs/reference/file-formats.md

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,12 +139,12 @@ Events present in the shared `hooks` block but unsupported by a given tool are s
139139
| `postToolUse` |||||||||||||||||
140140
| `preModelInvocation` |||||||||||||||||
141141
| `postModelInvocation` |||||||||||||||||
142-
| `postToolUseFailure` || |||||||||||||||
142+
| `postToolUseFailure` || |||||||||||||||
143143
| `stop` |||||||||||||||||
144-
| `subagentStart` || |||||||||||||||
144+
| `subagentStart` || |||||||||||||||
145145
| `subagentStop` |||||||||||||||||
146146
| `preCompact` |||||||||||||||||
147-
| `postCompact` || |||||||||||||||
147+
| `postCompact` || |||||||||||||||
148148
| `afterFileEdit` |||||||||||||||||
149149
| `beforeShellExecution` |||||||||||||||||
150150
| `afterShellExecution` |||||||||||||||||
@@ -165,6 +165,19 @@ Events present in the shared `hooks` block but unsupported by a given tool are s
165165
| `workspaceOpen` |||||||||||||||||
166166
| `messageDisplay` |||||||||||||||||
167167
| `afterError` |||||||||||||||||
168+
| `instructionsLoaded` |||||||||||||||||
169+
| `userPromptExpansion` |||||||||||||||||
170+
| `postToolBatch` |||||||||||||||||
171+
| `permissionDenied` |||||||||||||||||
172+
| `taskCreated` |||||||||||||||||
173+
| `taskCompleted` |||||||||||||||||
174+
| `stopFailure` |||||||||||||||||
175+
| `teammateIdle` |||||||||||||||||
176+
| `configChange` |||||||||||||||||
177+
| `cwdChanged` |||||||||||||||||
178+
| `fileChanged` |||||||||||||||||
179+
| `elicitation` |||||||||||||||||
180+
| `elicitationResult` |||||||||||||||||
168181

169182
> **Note:** `worktreeCreate`, `worktreeRemove`, and `messageDisplay` are Claude Code-specific events and do not support the `matcher` field. Any matcher defined in the config is ignored for these events.
170183

skills/rulesync/file-formats.md

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -139,12 +139,12 @@ Events present in the shared `hooks` block but unsupported by a given tool are s
139139
| `postToolUse` |||||||||||||||||
140140
| `preModelInvocation` |||||||||||||||||
141141
| `postModelInvocation` |||||||||||||||||
142-
| `postToolUseFailure` || |||||||||||||||
142+
| `postToolUseFailure` || |||||||||||||||
143143
| `stop` |||||||||||||||||
144-
| `subagentStart` || |||||||||||||||
144+
| `subagentStart` || |||||||||||||||
145145
| `subagentStop` |||||||||||||||||
146146
| `preCompact` |||||||||||||||||
147-
| `postCompact` || |||||||||||||||
147+
| `postCompact` || |||||||||||||||
148148
| `afterFileEdit` |||||||||||||||||
149149
| `beforeShellExecution` |||||||||||||||||
150150
| `afterShellExecution` |||||||||||||||||
@@ -165,6 +165,19 @@ Events present in the shared `hooks` block but unsupported by a given tool are s
165165
| `workspaceOpen` |||||||||||||||||
166166
| `messageDisplay` |||||||||||||||||
167167
| `afterError` |||||||||||||||||
168+
| `instructionsLoaded` |||||||||||||||||
169+
| `userPromptExpansion` |||||||||||||||||
170+
| `postToolBatch` |||||||||||||||||
171+
| `permissionDenied` |||||||||||||||||
172+
| `taskCreated` |||||||||||||||||
173+
| `taskCompleted` |||||||||||||||||
174+
| `stopFailure` |||||||||||||||||
175+
| `teammateIdle` |||||||||||||||||
176+
| `configChange` |||||||||||||||||
177+
| `cwdChanged` |||||||||||||||||
178+
| `fileChanged` |||||||||||||||||
179+
| `elicitation` |||||||||||||||||
180+
| `elicitationResult` |||||||||||||||||
168181

169182
> **Note:** `worktreeCreate`, `worktreeRemove`, and `messageDisplay` are Claude Code-specific events and do not support the `matcher` field. Any matcher defined in the config is ignored for these events.
170183

src/features/hooks/claudecode-hooks.test.ts

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,69 @@ describe("ClaudecodeHooks", () => {
7272
expect(parsed.hooks.afterFileEdit).toBeUndefined();
7373
});
7474

75+
it("should support the current documented Claude Code hook events (#1628)", async () => {
76+
await ensureDir(join(testDir, ".claude"));
77+
await writeFileContent(join(testDir, ".claude", "settings.json"), JSON.stringify({}));
78+
79+
const config = {
80+
version: 1,
81+
hooks: {
82+
instructionsLoaded: [{ command: "a.sh" }],
83+
userPromptExpansion: [{ command: "b.sh" }],
84+
postToolUseFailure: [{ command: "c.sh" }],
85+
postToolBatch: [{ command: "d.sh" }],
86+
permissionDenied: [{ command: "e.sh" }],
87+
subagentStart: [{ command: "f.sh" }],
88+
taskCreated: [{ command: "g.sh" }],
89+
taskCompleted: [{ command: "h.sh" }],
90+
stopFailure: [{ command: "i.sh" }],
91+
teammateIdle: [{ command: "j.sh" }],
92+
configChange: [{ command: "k.sh" }],
93+
cwdChanged: [{ command: "l.sh" }],
94+
fileChanged: [{ command: "m.sh" }],
95+
postCompact: [{ command: "n.sh" }],
96+
elicitation: [{ command: "o.sh" }],
97+
elicitationResult: [{ command: "p.sh" }],
98+
},
99+
};
100+
const rulesyncHooks = new RulesyncHooks({
101+
outputRoot: testDir,
102+
relativeDirPath: RULESYNC_RELATIVE_DIR_PATH,
103+
relativeFilePath: "hooks.json",
104+
fileContent: JSON.stringify(config),
105+
validate: false,
106+
});
107+
108+
const claudecodeHooks = await ClaudecodeHooks.fromRulesyncHooks({
109+
outputRoot: testDir,
110+
rulesyncHooks,
111+
validate: false,
112+
});
113+
114+
const parsed = JSON.parse(claudecodeHooks.getFileContent());
115+
// Each canonical event is emitted under its documented PascalCase name.
116+
for (const eventName of [
117+
"InstructionsLoaded",
118+
"UserPromptExpansion",
119+
"PostToolUseFailure",
120+
"PostToolBatch",
121+
"PermissionDenied",
122+
"SubagentStart",
123+
"TaskCreated",
124+
"TaskCompleted",
125+
"StopFailure",
126+
"TeammateIdle",
127+
"ConfigChange",
128+
"CwdChanged",
129+
"FileChanged",
130+
"PostCompact",
131+
"Elicitation",
132+
"ElicitationResult",
133+
]) {
134+
expect(parsed.hooks[eventName], `missing ${eventName}`).toBeDefined();
135+
}
136+
});
137+
75138
it("should only prefix dot-relative commands with $CLAUDE_PROJECT_DIR", async () => {
76139
await ensureDir(join(testDir, ".claude"));
77140
await writeFileContent(join(testDir, ".claude", "settings.json"), JSON.stringify({}));

0 commit comments

Comments
 (0)