Context Course documentation

Hook Events and the Agent Lifecycle

Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

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
Codex
OpenCode
Pi

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).
  • InstructionsLoadedCLAUDE.md files 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

Claude Code
Codex
OpenCode
Pi

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.

Claude Code
Codex
OpenCode
Pi

Exit codes (command hooks):

  • Exit 0 — allow, no change.
  • Exit 2 with a message on stderr — block or continue per event semantics (e.g. block the tool call on PreToolUse, erase the prompt on UserPromptSubmit).

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 callPreToolUse / tool.execute.before / tool_call.
  • Run a linter after an editPostToolUse (match Edit|Write) / tool.execute.after / tool_result.
  • Block dangerous commandsPreToolUse with exit code 2, a thrown error, or tool_call with { block: true }.
  • Inject repo context on each turnUserPromptSubmit with additionalContext (Claude Code / Codex), the event callback on message.updated (OpenCode), or before_agent_start (Pi).
  • Persist conversation stateStop / SessionEnd, session.idle on OpenCode, or agent_end / session_shutdown on Pi.
  • Add environment variables to shellsshell.env (OpenCode), or mutate tool_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