vault backup: 2026-04-17 12:53:08
This commit is contained in:
parent
b08260e27c
commit
72186070f9
5 changed files with 223 additions and 1 deletions
|
|
@ -170,3 +170,6 @@ tags: [daily]
|
|||
- 12:51 | `memory-compiler`
|
||||
- **Asked:** Compile a new article about Agent SDK hosting into the wiki knowledge base.
|
||||
- **Done:** Filed structured article as `agent-sdk/hosting-production.md` with deployment patterns, system requirements, and provider options.
|
||||
- 12:52 (1min) | `memory-compiler`
|
||||
- **Asked:** Asked to compile a new wiki article about agent loop mechanics into the knowledge base structure.
|
||||
- **Done:** Created structured wiki article documenting agent loop lifecycle, turn mechanics, message types, tools, and execution rules.
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ This 3-hop pattern works for hundreds of articles without vector search.
|
|||
| [[wiki/web-agency/_index\|web-agency/]] | AI-assisted website building & selling: Claude Code, Nanobanana 2, Kling, LaunchPath MCP | 1 |
|
||||
| [[wiki/dotfiles/_index\|dotfiles/]] | Linux terminal ricing: Kitty, Fish, WezTerm CLI, modern Rust CLI tools, LazyVim, unified themes, Tabby | 9 |
|
||||
|
||||
| [[wiki/agent-sdk/_index\|agent-sdk/]] | Claude Agent SDK (formerly Claude Code SDK) — build autonomous AI agents in Python and TypeScript | 12 |
|
||||
| [[wiki/agent-sdk/_index\|agent-sdk/]] | Claude Agent SDK (formerly Claude Code SDK) — build autonomous AI agents in Python and TypeScript | 13 |
|
||||
| [[wiki/llm-models/_index\|llm-models/]] | OpenAI model catalog — GPT-5.x, o-series reasoning, audio/realtime, embeddings, moderation | 1 |
|
||||
| [[wiki/claude-code/_index\|claude-code/]] | Claude Code product docs — install, capabilities, surfaces, MCP, hooks, scheduling, multi-agent, plugins, skills, error recovery | 7 |
|
||||
|
||||
|
|
|
|||
|
|
@ -26,3 +26,4 @@ Build production AI agents using the same tools, agent loop, and context managem
|
|||
| [[wiki/agent-sdk/custom-tools\|custom-tools]] | Define custom tools with @tool/@tool(), in-process MCP server, error handling, images, resources, annotations | raw/Give Claude custom tools.md | 2026-04-17 |
|
||||
| [[wiki/agent-sdk/user-input-approvals\|user-input-approvals]] | canUseTool callback: tool approvals, AskUserQuestion clarifying questions, allow/deny/modify, Python streaming workaround | raw/Handle approvals and user input.md | 2026-04-17 |
|
||||
| [[wiki/agent-sdk/hosting-production\|hosting-production]] | Production hosting: container requirements, 4 deployment patterns (ephemeral/long-running/hybrid/multi-agent), sandbox providers, ops notes | raw/Hosting the Agent SDK.md | 2026-04-17 |
|
||||
| [[wiki/agent-sdk/agent-loop\|agent-loop]] | Message lifecycle, turns, tool execution, context window, compaction, sessions, result subtypes, hooks | raw/How the agent loop works.md | 2026-04-17 |
|
||||
|
|
|
|||
218
wiki/agent-sdk/sdk-hooks.md
Normal file
218
wiki/agent-sdk/sdk-hooks.md
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
---
|
||||
title: "Intercept and Control Agent Behavior with SDK Hooks"
|
||||
aliases: [sdk-hooks, agent-hooks, hook-callbacks]
|
||||
tags: [agent-sdk, hooks, python, typescript, security, permissions]
|
||||
sources: [raw/Intercept and control agent behavior with hooks.md]
|
||||
created: 2026-04-17
|
||||
updated: 2026-04-17
|
||||
---
|
||||
|
||||
# Intercept and Control Agent Behavior with SDK Hooks
|
||||
|
||||
SDK hooks are async callback functions registered in `ClaudeAgentOptions` that fire at key execution points. Unlike [[wiki/agent-sdk/hooks-guide|shell command hooks]] (defined in settings files), these run in-process alongside your agent code.
|
||||
|
||||
## What Hooks Can Do
|
||||
|
||||
- **Block** dangerous operations before they execute (`permissionDecision: "deny"`)
|
||||
- **Modify** tool inputs before execution (`updatedInput`)
|
||||
- **Auto-approve** specific tools without user prompts (`permissionDecision: "allow"`)
|
||||
- **Log / audit** every tool call for compliance or debugging
|
||||
- **Inject context** into the conversation the model sees (`systemMessage`)
|
||||
- **Track lifecycle** — subagent start/stop, session boundaries, compaction
|
||||
|
||||
## Available Hook Events
|
||||
|
||||
| Hook Event | Python | TypeScript | Trigger |
|
||||
|---|---|---|---|
|
||||
| `PreToolUse` | Yes | Yes | Before tool executes — can block or modify |
|
||||
| `PostToolUse` | Yes | Yes | After tool succeeds |
|
||||
| `PostToolUseFailure` | Yes | Yes | After tool fails |
|
||||
| `UserPromptSubmit` | Yes | Yes | When user prompt is submitted |
|
||||
| `Stop` | Yes | Yes | Agent execution stops |
|
||||
| `SubagentStart` | Yes | Yes | Subagent initializes |
|
||||
| `SubagentStop` | Yes | Yes | Subagent completes |
|
||||
| `PreCompact` | Yes | Yes | Before conversation compaction |
|
||||
| `PermissionRequest` | Yes | Yes | Before permission dialog |
|
||||
| `Notification` | Yes | Yes | Agent status messages |
|
||||
| `SessionStart` | **No** | Yes | Session init (Python: use shell hooks instead) |
|
||||
| `SessionEnd` | **No** | Yes | Session ends |
|
||||
| `TaskCompleted` | **No** | Yes | Background task finishes |
|
||||
| `ConfigChange` | **No** | Yes | Config file changes |
|
||||
| `WorktreeCreate` | **No** | Yes | Git worktree created |
|
||||
| `WorktreeRemove` | **No** | Yes | Git worktree removed |
|
||||
|
||||
## Configuration
|
||||
|
||||
```python
|
||||
from claude_agent_sdk import ClaudeAgentOptions, HookMatcher
|
||||
|
||||
options = ClaudeAgentOptions(
|
||||
hooks={
|
||||
"PreToolUse": [HookMatcher(matcher="Write|Edit", hooks=[my_callback])]
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
- **Keys** = hook event names (case-sensitive: `PreToolUse` not `preToolUse`)
|
||||
- **Values** = list of `HookMatcher` objects
|
||||
|
||||
### Matchers
|
||||
|
||||
`HookMatcher(matcher="...", hooks=[...], timeout=60)`
|
||||
|
||||
- `matcher` — regex against tool name (e.g. `"Write|Edit"`, `"^mcp__"`, `"Bash"`)
|
||||
- Omit `matcher` to match all occurrences of the event
|
||||
- Matchers filter by **tool name only** — filter by file path inside the callback
|
||||
|
||||
**Built-in tool names:** `Bash`, `Read`, `Write`, `Edit`, `Glob`, `Grep`, `WebFetch`, `Agent`
|
||||
**MCP tools:** `mcp__<server>__<action>` (e.g. `mcp__playwright__browser_click`)
|
||||
|
||||
## Callback Inputs & Outputs
|
||||
|
||||
### Inputs (3 args)
|
||||
1. `input_data` — typed dict with event details; all share `session_id`, `cwd`, `hook_event_name`
|
||||
2. `tool_use_id` — correlates `PreToolUse` ↔ `PostToolUse` for the same call
|
||||
3. `context` — `AbortSignal` in TypeScript; reserved in Python
|
||||
|
||||
### Output Fields
|
||||
|
||||
| Field | Level | Effect |
|
||||
|---|---|---|
|
||||
| `systemMessage` | top-level | Injects text into conversation (model sees it) |
|
||||
| `continue` (`continue_` in Python) | top-level | Stop agent after this hook if `False` |
|
||||
| `hookSpecificOutput.permissionDecision` | nested | `"allow"` / `"deny"` / `"ask"` |
|
||||
| `hookSpecificOutput.permissionDecisionReason` | nested | Reason string shown to model |
|
||||
| `hookSpecificOutput.updatedInput` | nested | Replacement tool input (must also set `allow`) |
|
||||
| `hookSpecificOutput.additionalContext` | nested | Appended to tool result (PostToolUse only) |
|
||||
|
||||
Return `{}` to allow the operation unchanged.
|
||||
|
||||
**Priority:** `deny` > `ask` > `allow` — if any hook denies, the operation is blocked.
|
||||
|
||||
### Async (fire-and-forget) output
|
||||
|
||||
For side effects (logging, webhooks) that shouldn't block the agent:
|
||||
|
||||
```python
|
||||
async def async_hook(input_data, tool_use_id, context):
|
||||
asyncio.create_task(send_to_logging_service(input_data))
|
||||
return {"async_": True, "asyncTimeout": 30000}
|
||||
```
|
||||
|
||||
Cannot block, modify, or inject context — agent has already moved on.
|
||||
|
||||
## Common Patterns
|
||||
|
||||
### Block dangerous paths
|
||||
|
||||
```python
|
||||
async def protect_env_files(input_data, tool_use_id, context):
|
||||
file_path = input_data["tool_input"].get("file_path", "")
|
||||
if file_path.split("/")[-1] == ".env":
|
||||
return {
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": input_data["hook_event_name"],
|
||||
"permissionDecision": "deny",
|
||||
"permissionDecisionReason": "Cannot modify .env files",
|
||||
}
|
||||
}
|
||||
return {}
|
||||
```
|
||||
|
||||
### Redirect writes to sandbox
|
||||
|
||||
```python
|
||||
async def redirect_to_sandbox(input_data, tool_use_id, context):
|
||||
if input_data["tool_name"] == "Write":
|
||||
original = input_data["tool_input"].get("file_path", "")
|
||||
return {
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": input_data["hook_event_name"],
|
||||
"permissionDecision": "allow", # required with updatedInput
|
||||
"updatedInput": {**input_data["tool_input"], "file_path": f"/sandbox{original}"},
|
||||
}
|
||||
}
|
||||
return {}
|
||||
```
|
||||
|
||||
### Auto-approve read-only tools
|
||||
|
||||
```python
|
||||
async def auto_approve_read_only(input_data, tool_use_id, context):
|
||||
if input_data["tool_name"] in ["Read", "Glob", "Grep"]:
|
||||
return {
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": input_data["hook_event_name"],
|
||||
"permissionDecision": "allow",
|
||||
}
|
||||
}
|
||||
return {}
|
||||
```
|
||||
|
||||
### Chain hooks (single responsibility per hook)
|
||||
|
||||
```python
|
||||
options = ClaudeAgentOptions(
|
||||
hooks={
|
||||
"PreToolUse": [
|
||||
HookMatcher(hooks=[rate_limiter]),
|
||||
HookMatcher(hooks=[authorization_check]),
|
||||
HookMatcher(hooks=[input_sanitizer]),
|
||||
HookMatcher(hooks=[audit_logger]),
|
||||
]
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
Hooks execute in array order. Chain for complex logic, keep each focused.
|
||||
|
||||
### Forward notifications to Slack
|
||||
|
||||
```python
|
||||
async def notification_handler(input_data, tool_use_id, context):
|
||||
try:
|
||||
await asyncio.to_thread(_send_slack, input_data.get("message", ""))
|
||||
except Exception as e:
|
||||
print(f"Slack failed: {e}") # swallow — don't interrupt agent
|
||||
return {}
|
||||
|
||||
options = ClaudeAgentOptions(
|
||||
hooks={"Notification": [HookMatcher(hooks=[notification_handler])]}
|
||||
)
|
||||
```
|
||||
|
||||
Notification types: `permission_prompt`, `idle_prompt`, `auth_success`, `elicitation_dialog`.
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Problem | Fix |
|
||||
|---|---|
|
||||
| Hook not firing | Check event name case; verify matcher regex; check `max_turns` not hit |
|
||||
| Matcher too broad | Empty matcher fires on ALL tools — always use `matcher=` when possible |
|
||||
| `updatedInput` not applied | Must be inside `hookSpecificOutput`, must also include `permissionDecision: "allow"`, must include `hookEventName` |
|
||||
| `SessionStart`/`SessionEnd` missing in Python | Not in Python SDK — use shell hooks via `setting_sources=["project"]` |
|
||||
| Subagent permission storms | Use `PreToolUse` to auto-approve tools for subagents; they don't inherit parent permissions |
|
||||
| Recursive loops | `UserPromptSubmit` hooks spawning subagents can loop — check `agent_id` to guard |
|
||||
|
||||
## Key Takeaways
|
||||
|
||||
- SDK hooks (`HookMatcher` + async callbacks) run in-process; shell command hooks run in `.claude/settings.json` — they complement each other
|
||||
- `PreToolUse` is the workhorse: block, modify inputs, or auto-approve
|
||||
- Always return `{}` to pass through; never mutate `tool_input` in place — return a new object
|
||||
- `deny` beats `ask` beats `allow` — multiple hooks compose safely
|
||||
- `SessionStart`/`SessionEnd` are TypeScript-only in the SDK; Python gets them via shell hooks + `setting_sources`
|
||||
- Use async output (`async_: True`) for logging/webhook side effects to avoid blocking the agent
|
||||
- Matchers are tool-name regex only — filter by file path inside the callback body
|
||||
|
||||
## Related Articles
|
||||
|
||||
- [[wiki/agent-sdk/hooks-guide|hooks-guide]] — shell command hooks: all 25 events, exit codes, JSON output format
|
||||
- [[wiki/agent-sdk/configure-permissions|configure-permissions]] — permission modes, allow/deny rules, subagent inheritance
|
||||
- [[wiki/agent-sdk/python-api-reference|python-api-reference]] — full Python hook input/output type definitions
|
||||
- [[wiki/agent-sdk/typescript-api-reference|typescript-api-reference]] — full TypeScript hook input/output type definitions
|
||||
- [[wiki/agent-sdk/custom-tools|custom-tools]] — build tools to extend what the agent can call
|
||||
- [[wiki/agent-sdk/user-input-approvals|user-input-approvals]] — `canUseTool` callback for interactive approval flows
|
||||
|
||||
---
|
||||
|
||||
*Source: raw/Intercept and control agent behavior with hooks.md*
|
||||
Loading…
Add table
Reference in a new issue