Skip to content

Per-Node Hooks

DAG workflow nodes support a hooks field that attaches Claude Agent SDK hooks to individual nodes. Hooks fire during the node’s AI execution and can control tool behavior, inject context, modify inputs, and more.

Claude only — Codex nodes will warn and ignore hooks.

name: safe-migration
description: Generate SQL with guardrails
nodes:
- id: generate
prompt: "Generate a database migration for $ARGUMENTS"
hooks:
PreToolUse:
- matcher: "Bash"
response:
hookSpecificOutput:
hookEventName: PreToolUse
permissionDecision: deny
permissionDecisionReason: "No shell access during SQL generation"

Each hook matcher has three fields:

  • matcher (optional): Regex pattern to filter by tool name. Omit to match all tools.
  • response (required): The SDK SyncHookJSONOutput returned when the hook fires.
  • timeout (optional): Seconds before the hook times out (default: 60).

At runtime, each YAML hook is wrapped in a trivial callback:

async () => response

No custom DSL — response IS the SDK type, passed through unchanged.

Important: When using hookSpecificOutput, you must include a hookEventName field that matches the event key (e.g., hookEventName: PreToolUse inside a PreToolUse hook). This is an SDK requirement — it uses this field to determine which event-specific fields to process.

EventFires WhenMatcher Filters On
PreToolUseBefore a tool executesTool name (e.g. Bash, Write, Read)
PostToolUseAfter a tool succeedsTool name
PostToolUseFailureAfter a tool failsTool name
NotificationSystem notificationNotification type
StopAgent stopsN/A
SubagentStartSubagent spawnedAgent type
SubagentStopSubagent finishesAgent type
PreCompactBefore context compactionTrigger (manual/auto)
SessionStartSession beginsSource (startup/resume/clear/compact)
SessionEndSession endsExit reason
UserPromptSubmitUser prompt submittedN/A
PermissionRequestPermission prompt would appearTool name
SetupSDK initializationTrigger (init/maintenance)
TeammateIdleAgent teammate goes idleN/A
TaskCompletedBackground task finishesN/A
ElicitationMCP server requests user inputN/A
ElicitationResultElicitation response receivedN/A
ConfigChangeSettings/config file changedSource (user_settings/project_settings/etc.)
WorktreeCreateGit worktree createdWorktree name
WorktreeRemoveGit worktree removedWorktree path
InstructionsLoadedCLAUDE.md/instructions loadedMemory type (User/Project/Local/Managed)

Tool names: Bash, Read, Write, Edit, Glob, Grep, WebFetch, Agent, plus MCP tools as mcp__<server>__<action>.

The response object supports these fields:

FieldTypeEffect
hookSpecificOutputobjectEvent-specific response (see below)
systemMessagestringInject a message visible to the model
continuebooleanfalse stops the agent
decision'approve' / 'block'Top-level approve/block
stopReasonstringReason when stopping
suppressOutputbooleanSuppress output emission
hookSpecificOutput:
hookEventName: PreToolUse
permissionDecision: deny | allow | ask # Control whether tool runs
permissionDecisionReason: "..." # Why (shown in logs)
updatedInput: # Modify tool arguments
file_path: "/sandbox/output.ts"
additionalContext: "..." # Text injected into model context
hookSpecificOutput:
hookEventName: PostToolUse
additionalContext: "..." # Text injected after tool result
updatedMCPToolOutput: ... # Override what model sees from tool
hookSpecificOutput:
hookEventName: PostToolUseFailure
additionalContext: "..." # Context after tool failure
hookSpecificOutput:
hookEventName: Elicitation
action: accept | decline | cancel # Respond to MCP elicitation
content: { ... } # Form field values
hookSpecificOutput:
hookEventName: ElicitationResult
action: accept | decline | cancel # Override elicitation result
content: { ... } # Modified response values
hooks:
PreToolUse:
- matcher: "Bash"
response:
hookSpecificOutput:
hookEventName: PreToolUse
permissionDecision: deny
permissionDecisionReason: "Shell access not allowed in this node"
hooks:
PreToolUse:
- matcher: "Write|Edit"
response:
hookSpecificOutput:
hookEventName: PreToolUse
permissionDecision: deny
permissionDecisionReason: "Only read operations are allowed — do not modify files"

Inject context before tool use (without blocking)

Section titled “Inject context before tool use (without blocking)”

Note: this does NOT block the tool — it adds guidance the model sees before the tool runs.

hooks:
PreToolUse:
- matcher: "Write|Edit"
response:
hookSpecificOutput:
hookEventName: PreToolUse
additionalContext: "Only write to files in the src/ directory"
hooks:
PreToolUse:
- matcher: "Write"
response:
hookSpecificOutput:
hookEventName: PreToolUse
permissionDecision: allow
updatedInput:
file_path: "/sandbox/output.ts"

Inject steering instructions after every tool call

Section titled “Inject steering instructions after every tool call”
hooks:
PostToolUse:
- response:
systemMessage: "Check: is this output relevant to the task? If not, stop and explain why."
hooks:
PostToolUse:
- matcher: "Read"
response:
hookSpecificOutput:
hookEventName: PostToolUse
additionalContext: "You just read a file. Do NOT modify it — analysis only."
hooks:
PreToolUse:
- matcher: "Bash"
response:
continue: false
stopReason: "Emergency halt — shell access attempted"
hooks:
PreToolUse:
- matcher: "Bash"
response:
hookSpecificOutput:
hookEventName: PreToolUse
permissionDecision: deny
permissionDecisionReason: "No shell"
- matcher: "Write|Edit"
response:
hookSpecificOutput:
hookEventName: PreToolUse
additionalContext: "Only write to files in src/"
PostToolUse:
- response:
systemMessage: "Verify output before continuing"
name: safe-code-review
description: Review code with guardrails
nodes:
- id: fetch-diff
bash: "git diff main...HEAD"
- id: review
prompt: "Review this diff for bugs and security issues: $fetch-diff.output"
depends_on: [fetch-diff]
hooks:
PreToolUse:
- matcher: "Bash"
response:
hookSpecificOutput:
hookEventName: PreToolUse
permissionDecision: deny
permissionDecisionReason: "Code review should not execute commands"
- matcher: "Write|Edit"
response:
hookSpecificOutput:
hookEventName: PreToolUse
permissionDecision: deny
permissionDecisionReason: "Code review is read-only"
PostToolUse:
- matcher: "Read"
response:
hookSpecificOutput:
hookEventName: PostToolUse
additionalContext: "Focus on security issues in this file"
- id: summarize
prompt: "Summarize the review findings from $review.output"
depends_on: [review]
allowed_tools: []
Featureallowed_tools/denied_toolshooks
Block a tool entirelyYesYes
Inject contextNoYes (additionalContext, systemMessage)
Modify tool inputNoYes (updatedInput)
Override tool outputNoYes (updatedMCPToolOutput)
Stop the agentNoYes (continue: false)
React after tool useNoYes (PostToolUse)

Use allowed_tools/denied_tools for simple include/exclude. Use hooks when you need context injection, input modification, or post-tool-use reactions.

  • Static responses only in YAML — hooks return the same response every time. For conditional logic, use when: conditions on downstream nodes or gate execution with upstream bash nodes that emit structured output.
  • Claude only — Codex nodes warn and ignore hooks.
  • No hook event streaming — hook lifecycle events (hook_started, hook_progress) are not forwarded to the Web UI.

Refer to the Anthropic Claude Agent SDK documentation for the authoritative SyncHookJSONOutput type, hook event reference, and matcher patterns.