Skip to content

Commit 4128661

Browse files
authored
Merge pull request #69 from syou6162/existing_hook_type
feat: extend existing hook types to align with official Claude Code specification
2 parents fe06981 + c631b86 commit 4128661

File tree

10 files changed

+1065
-34
lines changed

10 files changed

+1065
-34
lines changed

CLAUDE.md

Lines changed: 128 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ Available condition types:
252252
- `prompt_regex`: Supports regex patterns including OR conditions with `|`
253253
- `every_n_prompts`: Triggers every N prompts based on transcript file parsing (counts `type: "user"` entries)
254254
- **Session-specific** (SessionEnd):
255-
- `reason_is`: Matches session end reason ("clear", "logout", "prompt_input_exit", "other")
255+
- `reason_is`: Matches session end reason ("clear", "logout", "prompt_input_exit", "bypass_permissions_disabled", "other")
256256

257257
Template variables are available based on the event type and include fields from BaseInput, tool-specific data, and full jq query support.
258258

@@ -886,6 +886,133 @@ SessionEnd:
886886
command: "cleanup-session.sh" # Returns JSON with systemMessage
887887
```
888888

889+
## Recent Hook Type Extensions
890+
891+
The following extensions have been added to existing hook types to align with the official Claude Code hooks specification:
892+
893+
### SessionStart Input Fields
894+
895+
SessionStart hooks now receive additional input fields:
896+
897+
- **`agent_type`** (string, optional): The agent name when Claude Code is started with `claude --agent <name>`
898+
- **`model`** (string): The model ID being used (e.g., "claude-sonnet-4-5-20250929")
899+
900+
These fields are available in template variables and can be used in conditions or command templates:
901+
902+
```yaml
903+
SessionStart:
904+
- actions:
905+
- type: output
906+
message: "Starting session with agent: {.agent_type}, model: {.model}"
907+
```
908+
909+
### PreToolUse Additional Context
910+
911+
PreToolUse hooks can now return `additionalContext` in their output:
912+
913+
**Output Action:**
914+
```yaml
915+
PreToolUse:
916+
- matcher: "Write"
917+
actions:
918+
- type: output
919+
message: "File validation passed"
920+
permission_decision: "allow"
921+
additional_context: "File meets security requirements"
922+
```
923+
924+
**Command Action JSON:**
925+
```json
926+
{
927+
"continue": true,
928+
"hookSpecificOutput": {
929+
"hookEventName": "PreToolUse",
930+
"permissionDecision": "allow",
931+
"additionalContext": "Additional information for Claude"
932+
}
933+
}
934+
```
935+
936+
The `additionalContext` field provides supplementary information to Claude that won't be shown to the user.
937+
938+
### Notification Hook Extensions
939+
940+
Notification hooks support matcher-based filtering and additional input fields:
941+
942+
**Matcher:**
943+
The `matcher` field filters notifications by `notification_type`:
944+
- `permission_prompt`: Permission request notifications
945+
- `idle_prompt`: Idle state notifications
946+
- `auth_success`: Authentication success notifications
947+
- `elicitation_dialog`: Elicitation dialog notifications
948+
949+
```yaml
950+
Notification:
951+
- matcher: "idle_prompt|permission_prompt"
952+
actions:
953+
- type: output
954+
message: "Handling user interaction notification"
955+
```
956+
957+
**Input Fields:**
958+
- **`title`** (string, optional): Notification title
959+
- **`notification_type`** (string): Type of notification
960+
961+
These fields are available in template variables:
962+
963+
```yaml
964+
Notification:
965+
- actions:
966+
- type: output
967+
message: "Notification: {.title} ({.notification_type})"
968+
```
969+
970+
### PostToolUse Updated MCP Tool Output
971+
972+
PostToolUse hooks can now replace MCP tool output using the `updatedMCPToolOutput` field:
973+
974+
**Command Action JSON:**
975+
```json
976+
{
977+
"continue": true,
978+
"hookSpecificOutput": {
979+
"hookEventName": "PostToolUse",
980+
"additionalContext": "Tool output was sanitized"
981+
},
982+
"updatedMCPToolOutput": "Sanitized output content"
983+
}
984+
```
985+
986+
**Important:** `updatedMCPToolOutput` is a **top-level field**, not part of `hookSpecificOutput`. It can be a string, object, or any JSON-serializable value.
987+
988+
When multiple actions execute, the last non-nil `updatedMCPToolOutput` value wins.
989+
990+
**Example Use Cases:**
991+
- Sanitizing sensitive information from tool output
992+
- Reformatting tool output for better readability
993+
- Adding metadata to MCP tool responses
994+
995+
### SessionEnd Reason
996+
997+
SessionEnd hooks now recognize the `bypass_permissions_disabled` reason value:
998+
999+
```yaml
1000+
SessionEnd:
1001+
- conditions:
1002+
- type: reason_is
1003+
value: "bypass_permissions_disabled"
1004+
actions:
1005+
- type: output
1006+
message: "Session ended due to bypass permissions being disabled"
1007+
```
1008+
1009+
Available reason values:
1010+
- `clear`: User cleared the session
1011+
- `logout`: User logged out
1012+
- `prompt_input_exit`: User exited via prompt input
1013+
- `bypass_permissions_disabled`: Session ended because bypass permissions were disabled
1014+
- `other`: Other reasons
1015+
8891016
## Common Workflows
8901017

8911018
### Adding a New Hook Type

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ For example, a simple Stop hook that sends notifications via [ntfy](https://ntfy
6161
- **Error Handling**: Robust error handling for unknown condition types
6262
- **Dry-Run Mode**: Test configurations before deployment
6363
- **Performance**: Cached jq query compilation for efficient template processing
64+
- **Extended Hook Support**: Full support for Claude Code hooks specification including:
65+
- SessionStart input fields (agent_type, model)
66+
- PreToolUse additional context output
67+
- Notification matcher filtering and extended input fields
68+
- PostToolUse MCP tool output replacement
69+
- SessionEnd bypass_permissions_disabled reason
6470

6571
## Installation
6672

executor.go

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1055,6 +1055,7 @@ func (e *ActionExecutor) ExecutePreToolUseAction(action Action, input *PreToolUs
10551055
Continue: true,
10561056
PermissionDecision: permissionDecision,
10571057
PermissionDecisionReason: cmdOutput.HookSpecificOutput.PermissionDecisionReason,
1058+
AdditionalContext: cmdOutput.HookSpecificOutput.AdditionalContext,
10581059
UpdatedInput: cmdOutput.HookSpecificOutput.UpdatedInput,
10591060
StopReason: cmdOutput.StopReason,
10601061
SuppressOutput: cmdOutput.SuppressOutput,
@@ -1096,11 +1097,18 @@ func (e *ActionExecutor) ExecutePreToolUseAction(action Action, input *PreToolUs
10961097
permissionDecision = *action.PermissionDecision
10971098
}
10981099

1100+
// Process additional_context if present
1101+
var additionalContext string
1102+
if action.AdditionalContext != nil {
1103+
additionalContext = unifiedTemplateReplace(*action.AdditionalContext, rawJSON)
1104+
}
1105+
10991106
return &ActionOutput{
11001107
Continue: true,
11011108
PermissionDecision: permissionDecision,
11021109
HookEventName: "PreToolUse",
11031110
PermissionDecisionReason: processedMessage,
1111+
AdditionalContext: additionalContext,
11041112
}, nil
11051113
}
11061114

@@ -1205,14 +1213,15 @@ func (e *ActionExecutor) ExecutePostToolUseAction(action Action, input *PostTool
12051213

12061214
// Build ActionOutput from parsed JSON
12071215
return &ActionOutput{
1208-
Continue: true,
1209-
Decision: decision,
1210-
Reason: cmdOutput.Reason,
1211-
StopReason: cmdOutput.StopReason,
1212-
SuppressOutput: cmdOutput.SuppressOutput,
1213-
SystemMessage: cmdOutput.SystemMessage,
1214-
HookEventName: hookEventName,
1215-
AdditionalContext: additionalContext,
1216+
Continue: true,
1217+
Decision: decision,
1218+
Reason: cmdOutput.Reason,
1219+
StopReason: cmdOutput.StopReason,
1220+
SuppressOutput: cmdOutput.SuppressOutput,
1221+
SystemMessage: cmdOutput.SystemMessage,
1222+
HookEventName: hookEventName,
1223+
AdditionalContext: additionalContext,
1224+
UpdatedMCPToolOutput: cmdOutput.UpdatedMCPToolOutput,
12161225
}, nil
12171226

12181227
case "output":
@@ -1606,23 +1615,19 @@ func checkUnsupportedFieldsPostToolUse(stdout string) {
16061615
}
16071616

16081617
supportedFields := map[string]bool{
1609-
"continue": true,
1610-
"decision": true, // PostToolUse specific (top-level)
1611-
"reason": true, // PostToolUse specific (top-level)
1612-
"stopReason": true,
1613-
"suppressOutput": true,
1614-
"systemMessage": true,
1615-
"hookSpecificOutput": true, // PostToolUse supports hookSpecificOutput
1616-
// Note: updatedMCPToolOutput is NOT supported (MCP tools only)
1618+
"continue": true,
1619+
"decision": true, // PostToolUse specific (top-level)
1620+
"reason": true, // PostToolUse specific (top-level)
1621+
"stopReason": true,
1622+
"suppressOutput": true,
1623+
"systemMessage": true,
1624+
"hookSpecificOutput": true, // PostToolUse supports hookSpecificOutput
1625+
"updatedMCPToolOutput": true, // For MCP tools only: replaces the tool's output
16171626
}
16181627

16191628
for field := range data {
16201629
if !supportedFields[field] {
1621-
if field == "updatedMCPToolOutput" {
1622-
fmt.Fprintf(os.Stderr, "Warning: Field '%s' is not supported for PostToolUse hooks in cchook (MCP tools only)\n", field)
1623-
} else {
1624-
fmt.Fprintf(os.Stderr, "Warning: Field '%s' is not supported for PostToolUse hooks\n", field)
1625-
}
1630+
fmt.Fprintf(os.Stderr, "Warning: Field '%s' is not supported for PostToolUse hooks\n", field)
16261631
}
16271632
}
16281633
}

0 commit comments

Comments
 (0)