Playbook for designing Claude Agent SDK hooks (PreToolUse, PostToolUse, SessionStart, Stop) — selecting the right event, writing the handler contract, deciding advisory vs blocking behaviour, and avoiding the four common hook anti-patterns. Owned by agent-sdk-engineer.
How this skill is triggered — by the user, by Claude, or both
Slash command
/claude-app-engineering:agent-sdk-hook-designThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
- Adding a new PreToolUse or PostToolUse hook to a Claude Agent SDK session.
| Event | Fires | Typical use |
|---|---|---|
PreToolUse | Before a tool call is executed | Validate args, block dangerous calls, log intent |
PostToolUse | After a tool returns | Redact PII from results, log outcome, rate-limit |
SessionStart | When a new agent session opens | Inject context, warm up state, validate auth |
Stop | When the agent concludes (any reason) | Audit log, cleanup ephemeral resources |
Decision: should the hook STOP execution on a violation?
├── YES (blocking) — exit with a non-zero code / raise + return deny verdict
│ Use for: security controls, destructive-action gates, required-field validation
│ Rule: blocking hooks must fail FAST (< 200 ms); never block on network I/O
└── NO (advisory) — log to stderr and exit 0
Use for: cost warnings, style guidance, telemetry
Rule: advisory hooks must NEVER cause data loss if ignored
Every hook handler must state:
*) it fires on.# Example: PreToolUse blocking hook — deny file writes outside /tmp
import json, sys
payload = json.load(sys.stdin)
tool_name = payload.get("tool", {}).get("name", "")
tool_input = payload.get("tool", {}).get("input", {})
if tool_name == "write_file":
path = tool_input.get("path", "")
if not path.startswith("/tmp/"):
print(json.dumps({"decision": "deny",
"reason": f"write_file outside /tmp is not allowed: {path}"}))
sys.exit(1)
sys.exit(0)
| Anti-pattern | Fix |
|---|---|
Hook fires on * (all tools) for a write-specific check | Scope to write_file, edit_file, etc. |
| Hook reads the full message history on every call | Read only tool.input; access history only in SessionStart |
| PostToolUse hook mutates the tool result | Return annotated metadata to stderr; never mutate — the model already processed the result |
| Hook makes a synchronous HTTP call in the hot path | Cache, pre-fetch in SessionStart, or make the hook advisory |
Hooks may fire multiple times on retried tool calls. Design for idempotency:
payload.json — both allow and deny branches.PreToolUse deny message surfaces to the agent (not swallowed by the SDK runtime).PostToolUse to decide whether to allow a tool call — the tool already executed; PostToolUse cannot undo writes.hooks.json and the session-level config) — both fire; effects double.PreToolUse for computer-use tools fires before the screen action — keep blocking logic tight or you'll desynchronise the computer-use state machine.npx claudepluginhub mcorbett51090/ravenclaude --plugin claude-app-engineeringCreates structured, bite-sized implementation plans from specs or requirements before writing code. Useful for breaking down multi-step tasks into testable steps with file structure and task boundaries.