Context Course documentation
Hook Events and the Agent Lifecycle
Hook Events and the Agent Lifecycle
Before wiring anything up, it helps to know which events actually fire and when. The vocabulary is slightly different on each platform, but the lifecycle they describe is shared.
The Shared Lifecycle
At the abstract level, every agent session moves through the same phases:
┌─────────────────────────────────────────────┐
│ Session starts │ ← SessionStart
├─────────────────────────────────────────────┤
│ Repeat for each turn: │
│ User submits a prompt │ ← UserPromptSubmit
│ Model reasons and may call tools │
│ Before each tool call │ ← PreToolUse
│ After each tool call │ ← PostToolUse
│ Turn ends │ ← Stop
├─────────────────────────────────────────────┤
│ Session ends │ ← SessionEnd
└─────────────────────────────────────────────┘This is the shared mental model. The platforms in this course map onto these moments with different names, different configuration surfaces, and a few gaps or extra events.
Events by Platform
Claude Code has the richest event set of the four platforms. Every event uses PascalCase and is configured in .claude/settings.json (or inside a plugin’s hooks/hooks.json).
Core lifecycle events:
SessionStart— New session begins (fresh, resume, or compaction continuation).InstructionsLoaded—CLAUDE.mdfiles have been loaded.UserPromptSubmit— User submitted a prompt, before the model sees it.PreToolUse— Before a tool call runs.PermissionRequest— Permission prompt about to be shown.PermissionDenied— A tool call was denied by the auto-mode classifier.PostToolUse— After a tool call returns.PostToolUseFailure— Tool call failed.Stop— Turn finished.SessionEnd— Session closed.
Subagent and task events:
SubagentStart,SubagentStop— A delegated subagent starts or finishes.TaskCreated,TaskCompleted— The TodoWrite task list changes.
Environment events:
CwdChanged,FileChanged— Working directory or a tracked file changed.WorktreeCreate,WorktreeRemove— Git worktree created or removed.PreCompact,PostCompact— Context compaction about to happen / just finished.ConfigChange,Notification— Settings changed; Claude raised a notification.
Matcher groups filter on event-specific fields. Tool events match tool names (for example, "matcher": "Bash" or "matcher": "Edit|Write"), while handler-level if conditions can use permission-rule syntax to inspect tool arguments (for example, "if": "Bash(rm *)").
Event Input: What Your Hook Receives
Command hooks receive a JSON payload on stdin. HTTP hooks receive the same payload as the POST body. Common fields across events:
{
"session_id": "...",
"transcript_path": "/path/to/transcript.jsonl",
"cwd": "/path/to/project",
"permission_mode": "default",
"hook_event_name": "PreToolUse"
}Tool-related events add tool_name, tool_input, and (on PostToolUse) tool_response. Subagent events add agent_id and agent_type. The project root is also available as an environment variable CLAUDE_PROJECT_DIR.
How Hooks Influence the Agent
Hooks are not purely observational — they can change what happens next.
Exit codes (command hooks):
- Exit
0— allow, no change. - Exit
2with a message on stderr — block or continue per event semantics (e.g. block the tool call onPreToolUse, erase the prompt onUserPromptSubmit).
JSON on stdout for finer control:
{
"hookSpecificOutput": {
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "No network access in this project"
}
}Also supported: top-level continue, stopReason, suppressOutput, systemMessage, and the legacy { "decision": "block", "reason": "..." } form.
HTTP hooks return the same JSON as a 2xx response body. Non-2xx responses or timeouts are treated as non-blocking errors.
Choosing the Right Event
A quick reference for common goals:
- Log every tool call →
PreToolUse/tool.execute.before/tool_call. - Run a linter after an edit →
PostToolUse(matchEdit|Write) /tool.execute.after/tool_result. - Block dangerous commands →
PreToolUsewith exit code2, a thrown error, ortool_callwith{ block: true }. - Inject repo context on each turn →
UserPromptSubmitwithadditionalContext(Claude Code / Codex), theeventcallback onmessage.updated(OpenCode), orbefore_agent_start(Pi). - Persist conversation state →
Stop/SessionEnd,session.idleon OpenCode, oragent_end/session_shutdownon Pi. - Add environment variables to shells →
shell.env(OpenCode), or mutatetool_call/ wrap user bash handling in an extension on Pi.
Key Takeaways
The platforms expose the same underlying lifecycle with different vocabulary and different levels of granularity. Claude Code has the richest event set and supports four handler types. Codex has a smaller, tightly focused set behind a feature flag. OpenCode folds hooks into its plugin system as typed function keys rather than a JSON config. Pi uses extension events such as before_agent_start, tool_call, and tool_result. Once you pick an event for the goal at hand, the rest of the work is the same across them.
Next, a quick quiz before we wire these events into a live Gradio dashboard.
Update on GitHub