Claude Code¶
This page documents the current Claude Code implementation in Agent Hooks.
Quick Setup¶
Install the CLI:
Put this in ~/.claude/settings.json for a global setup, or in .claude/settings.json for a project-local setup:
{
"hooks": {
"PermissionRequest": [
{
"hooks": [
{
"type": "command",
"command": "agent-hooks callback --provider claude-code"
}
]
}
],
"Notification": [
{
"matcher": "permission_prompt",
"hooks": [
{
"type": "command",
"command": "agent-hooks callback --provider claude-code"
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "agent-hooks callback --provider claude-code"
}
]
}
],
"StopFailure": [
{
"hooks": [
{
"type": "command",
"command": "agent-hooks callback --provider claude-code"
}
]
}
],
"PreToolUse": [
{
"matcher": "AskUserQuestion",
"hooks": [
{
"type": "command",
"command": "agent-hooks callback --provider claude-code"
}
]
}
]
}
}
This setup wires the built-in callback into the Claude flows Agent Hooks handles best today:
PermissionRequestfor local allow or deny decisionsNotificationwithmatcher: "permission_prompt"so attention requests also surface locallyStopandStopFailurefor local completion or error visibilityPreToolUsewithmatcher: "AskUserQuestion"so the native picker fires for every question (including auto-allowed tool calls that skipPermissionRequest)
Why the explicit provider flag
Use --provider claude-code in the hook command even though Claude payloads are often identifiable on their own. It keeps the callback wiring explicit and easier to debug later.
Raw Event Coverage¶
The Claude adapter currently normalizes these raw events into first-class shared event names:
NotificationPermissionRequestPreToolUse(only whentool_name == "AskUserQuestion"; normalized toPermissionRequestso the picker flow handles it)StopStopFailure
The matcher also recognizes a broader set of Claude-specific raw event names so payload detection can stay correct even when the built-in app does not turn them into dedicated behavior yet.
Built-in App Behavior¶
The built-in callback app gives Claude Code:
- notification rendering for
Notification - permission dialogs for
PermissionRequest - the AskUserQuestion picker for
PreToolUseonAskUserQuestion - completion notifications for
Stop - error notifications for
StopFailure
Permission Handling¶
Claude permission requests carry permission_suggestions describing the session
rules the user could persist (for example "this exact command", "all git *
commands", or "all Bash commands"). Agent Hooks renders each suggestion as its own
choice instead of collapsing them behind a single Always Allow button.
Permission Picker¶
When Claude supplies at least one permission_suggestion, the built-in app shows a
native choose from list picker whose choices are:
Allow once- allow the call without persisting a rule- one entry per suggestion, labeled with the rule(s) it would persist, shown exactly
as Claude sent them (for example
Bash(git *),Edit(src/**), Write(src/**), or a non-rule scope such asmode: acceptEdits)
Every entry below Allow once is an always-allow choice, so the labels carry no
redundant "Always allow" prefix. The picker's buttons are Select and Deny.
Selecting a rule entry returns behavior: "allow" with updatedPermissions
containing only the chosen suggestion, scoped to destination session. Selecting
Allow once allows the call with no updatedPermissions. Pressing Deny (or
dismissing the picker) denies the call.
Because only the selected suggestion is persisted, the user can pick the exact scope they want rather than accepting every offered rule at once.
Standard Dialog Fallback¶
When the picker is unavailable - non-macOS hosts, osascript disabled via
AGENT_HOOK_DISABLE_OSASCRIPT=1, or a picker transport failure - Agent Hooks falls
back to the standard Deny / Allow Once / Always Allow dialog. The dialog message
lists every suggestion rule so the full scope stays visible, and its session-scoped
Always Allow applies all offered suggestions. Permission requests that arrive without
any permission_suggestions use this same dialog.
That means the built-in Claude flow can preview and apply session rules without inventing its own permission format.
AskUserQuestion Picker¶
When Claude Code calls the built-in AskUserQuestion tool, Agent Hooks intercepts the request and shows a native macOS picker for each question instead of the standard Allow Once / Always Allow / Deny dialog. The picker:
- uses radio-style single selection for
multiSelect: falsequestions and shift-/cmd-click multi-selection formultiSelect: truequestions - shows the question text and each option's
descriptionas the prompt body - exposes
SubmitandCancelbuttons in place of the standard permission buttons
When the user clicks Submit, the hook responds with permissionDecision: "allow" (PreToolUse) or decision: { behavior: "allow" } (PermissionRequest) together with an updatedInput block that contains the original questions plus an answers map keyed by question text. Claude Code consumes those answers directly and skips its built-in TUI picker, so the user only picks once.
When the user clicks Cancel, the hook responds with a deny decision and the request is dropped.
Required hook wiring¶
To enable the picker, the hook must run for AskUserQuestion invocations. The Quick Setup snippet already includes the required entry:
"PreToolUse": [
{
"matcher": "AskUserQuestion",
"hooks": [
{
"type": "command",
"command": "agent-hooks callback --provider claude-code"
}
]
}
]
PreToolUse is required because AskUserQuestion can be auto-allowed (for example when a skill's allowed-tools lists it), in which case PermissionRequest never fires and the picker would be bypassed. The matcher narrows the hook to AskUserQuestion so other tool calls keep their normal PermissionRequest flow.
If you also keep the PermissionRequest hook from the Quick Setup, Agent Hooks deduplicates: whichever event fires first opens the picker, and the response carries the answers back through whichever wire shape Claude Code expects for that event.
Fallback¶
If osascript is unavailable (non-macOS hosts, sandboxed environments, or AGENT_HOOK_DISABLE_OSASCRIPT=1), Agent Hooks falls back to the standard permission dialog with a text preview of the questions so the request is still actionable.
Try it¶
To exercise this picker locally, ask Claude Code something like "Ask me a couple of multi-option questions to test the picker." A separate native dialog opens for each question, and the answers flow back to Claude Code without its TUI prompting again.
Response Rendering¶
The Claude renderer currently supports the top-level fields already modeled in the shared response:
suppressOutputcontinuestopReasonsystemMessagedecisionreasonhookSpecificOutput
For permission requests, the important block is the Claude decision payload inside hookSpecificOutput.
Current Limitations¶
- The matcher recognizes more Claude raw events than the current normalization table exposes as first-class built-in behavior.
SessionStart,PostToolUse, andUserPromptSubmitare not part of the Claude adapter's normalized event surface today.- The docs site intentionally does not describe unsupported Claude behaviors that the current code does not implement.
Practical Takeaway¶
If your Claude Code usage is centered on:
- local permission prompts
- notifications
- stop-state visibility
the built-in app is already useful. If you need more Claude event-specific logic, build a custom AgentHook app and work from the normalized payload plus payload.raw.