Events & Results
Event
Hook lifecycle events that can trigger registered hooks.
Usage
Event()Combinable with | to match multiple events in a single hook registration.
Example
>>> hook(Event.Stop | Event.SubagentStop, message="review first", block=True)BaseHookEvent
Base class for all hook events, providing access to raw payload, context, and convenience methods.
Usage
BaseHookEvent(_raw, ctx)Parameter Attributes
_raw: dict[str, Any]ctx: HookContext
Attributes
| Name | Description |
|---|---|
| agent_id | The subagent id attached to this event, or None for main-agent events. |
| background_tasks |
The background tasks keeping this session alive, () when the payload omits them.
|
| cmd |
The event’s Bash command as a walkable ~captain_hook.cmd.Cmd.
|
| command |
The event’s Bash command as a walkable ~captain_hook.cmd.Cmd — an alias for cmd.
|
| is_subagent | Whether this event came from a subagent payload. |
| session_crons |
The scheduled prompts registered on this session, () when the payload omits them.
|
| skip_permissions |
Whether the session’s claude process was launched with permission bypass available.
|
| tasks | The live task list for this session, read from Claude Code’s native task store. |
agent_id
The subagent id attached to this event, or None for main-agent events.
agent_id: str | None
A missing, null, or empty agent_id all mean the main agent.
background_tasks
The background tasks keeping this session alive, () when the payload omits them.
background_tasks: tuple[BackgroundTask, …]
Populated only on Stop and SubagentStop events from Claude Code v2.1.145+; a non-empty tuple means the session is paused waiting for background work to wake it, not done.
cmd
The event’s Bash command as a walkable ~captain_hook.cmd.Cmd.
cmd: Cmd
Always a Cmd — an empty or non-Bash command yields one with zero calls — so a handler iterates evt.cmd.calls(...) or tests evt.cmd.call(...) without a preceding guard. The raw command text is evt.cmd.raw (or str(evt.cmd)); evt.cmd.q and evt.cmd.line expose the parsed structure. On a rewrite-capable event, rewrites composed via ~captain_hook.cmd.Call.sub() splice back through it.
command
The event’s Bash command as a walkable ~captain_hook.cmd.Cmd — an alias for cmd.
command: Cmd
Always a Cmd (empty for a non-Bash or absent command). The raw command text is evt.command.raw or str(evt.command).
is_subagent
Whether this event came from a subagent payload.
is_subagent: bool
session_crons
The scheduled prompts registered on this session, () when the payload omits them.
session_crons: tuple[SessionCron, …]
Populated only on Stop and SubagentStop events from Claude Code v2.1.145+; a non-empty tuple means the session is paused for future scheduled work.
skip_permissions
Whether the session’s claude process was launched with permission bypass available.
skip_permissions: bool
Walks the process tree to the nearest claude ancestor and reports whether that process was started with --dangerously-skip-permissions or --allow-dangerously-skip-permissions.
tasks
The live task list for this session, read from Claude Code’s native task store.
tasks: Tasks
Unlike transcript-derived task_ops(), this reflects updates made by subagents, teammates, or resumed sessions, and is empty when the session has no task store — it never falls back to another session’s tasks.
Methods
| Name | Description |
|---|---|
| as_input() |
Return input narrowed to call_type, or None when it is a different tool call.
|
| context() | Emit advisory context exactly like warn(), but without pre-approving the tool. |
| llm() | Ask an LLM a question about this event and return a typed answer. |
| warn() | Emit a warning whose parts are auto-rendered and joined with newlines. |
as_input()
Return input narrowed to call_type, or None when it is a different tool call.
Usage
as_input(call_type)context()
Emit advisory context exactly like warn(), but without pre-approving the tool.
Usage
context(*parts)Renders and joins parts through the same path as warn(), then returns a warn-action result with approve=False. On PreToolUse this drops the permissionDecision: allow rider a plain warn carries, so the message surfaces as pure additionalContext and Claude Code’s own permission flow still decides the tool.
llm()
Ask an LLM a question about this event and return a typed answer.
Usage
llm(self, prompt: str | Prompt, model: None = None, contexts: Sequence[PromptContext] = (), signals: Sequence[Signal | NlpSignal] | Signals | None = None, when: Callable[[BaseHookEvent], bool] | None = None, hook: str | None = None, retries: int = 2, max_context: int = 2000, specialty: TSpecialty = 'review', size: TModel = 'small', agent: bool = False, transcript: bool | int | Literal['recent', 'full'] = False, diff: bool | str = False) -> str | None
llm(self, prompt: str | Prompt, model: type[bool], contexts: Sequence[PromptContext] = (), signals: Sequence[Signal | NlpSignal] | Signals | None = None, when: Callable[[BaseHookEvent], bool] | None = None, hook: str | None = None, retries: int = 2, max_context: int = 2000, specialty: TSpecialty = 'review', size: TModel = 'small', agent: bool = False, transcript: bool | int | Literal['recent', 'full'] = False, diff: bool | str = False) -> bool | None
llm(self, prompt: str | Prompt, model: type[int], contexts: Sequence[PromptContext] = (), signals: Sequence[Signal | NlpSignal] | Signals | None = None, when: Callable[[BaseHookEvent], bool] | None = None, hook: str | None = None, retries: int = 2, max_context: int = 2000, specialty: TSpecialty = 'review', size: TModel = 'small', agent: bool = False, transcript: bool | int | Literal['recent', 'full'] = False, diff: bool | str = False) -> int | None
llm(self, prompt: str | Prompt, model: type[M], contexts: Sequence[PromptContext] = (), signals: Sequence[Signal | NlpSignal] | Signals | None = None, when: Callable[[BaseHookEvent], bool] | None = None, hook: str | None = None, retries: int = 2, max_context: int = 2000, specialty: TSpecialty = 'review', size: TModel = 'small', agent: bool = False, transcript: bool | int | Literal['recent', 'full'] = False, diff: bool | str = False) -> M | NoneThe public sugar over the LLM stack: delegates to [llm_evaluate][captain_hook.llm_evaluate] (contexts, signals, once-per-turn throttling, retries with validation feedback), which calls the backend through ctx.call_llm. model picks the answer shape: None returns the raw str reply, bool/int coerce through a single-field answer schema, and a BaseModel subclass returns its validated instance. size picks the model tier ("small"/"medium"/"large").
A skipped call — already fired this turn, or a required context came up empty — returns None; a call that still fails after retries re-asks raises.
Example
>>> if evt.llm("Is this print() call debug leftovers?", bool):
... return evt.warn("Drop the print before finishing.")warn()
Emit a warning whose parts are auto-rendered and joined with newlines.
Usage
warn(*parts)Each part is rendered by form: a plain str passes through verbatim; a (label, value) tuple becomes "{label}: {json}" with value JSON-encoded; any other object is JSON-encoded directly. Rendered parts are joined with "\n".
Parameters
*parts: str | tuple[str, object] | object-
Warning fragments, each a
str, a(label, value)tuple, or any JSON-serializable object.
Returns
HookResult- A warn HookResult carrying the joined message.
ToolHookEvent
Event for tool-related hooks, adding tool name, input, command, and file access.
Usage
ToolHookEvent(_raw, ctx)Parameter Attributes
_raw: dict[str, Any]ctx: HookContext
Attributes
| Name | Description |
|---|---|
| edit |
Structured before/after view of the pending edit, or None.
|
| post_image | The full post-edit file image an Edit/MultiEdit/Write would produce, at PreToolUse. |
| pre_image | The full pre-edit file image for an Edit/MultiEdit/Write/span-edit, read from disk at PreToolUse. |
| replaced | The pre-image of the text this call overwrites, when knowable. |
edit
Structured before/after view of the pending edit, or None.
edit: Edit | None
post_image
The full post-edit file image an Edit/MultiEdit/Write would produce, at PreToolUse.
post_image: str | None
A Write is its content; an Edit applies old→new to pre_image (once, or every occurrence when replace_all); a MultiEdit folds its spans in order. A registered MCP span-edit tool yields None: its opaque locator can’t be resolved to a position without the tool, so the post-image can’t be simulated. None too when a span’s old is absent from the running image (the tool call would fail), off PreToolUse, or for other tools.
pre_image
The full pre-edit file image for an Edit/MultiEdit/Write/span-edit, read from disk at PreToolUse.
pre_image: str | None
None off PreToolUse — disk already holds the post-edit text — and for other tools. Unlike replaced (an Edit’s old fragment), this is always the whole file, so a comment run’s full extent is visible even when the edit touched only part of it.
replaced
The pre-image of the text this call overwrites, when knowable.
replaced: str | None
An Edit’s old and a MultiEdit’s newline-joined olds carry the pre-image at any event. A Write’s pre-image is the file’s current on-disk content — "" for a new file, lossily decoded for non-UTF-8 bytes, None for a directory or unreadable path — and only at PreToolUse: once the Write lands, disk already holds the new text, so any other event yields None. A registered MCP span-edit tool follows the Write arm — its opaque locator carries no old fragment, so the whole on-disk file stands in as a conservative pre-image (a superset only suppresses, never misreports, an introduced construct). None for other tools.
ToolRewriteEvent
Tool event whose input can be rewritten before it runs.
Usage
ToolRewriteEvent(_raw, ctx)Base for the events where Claude Code accepts an updatedInput decision (PreToolUse and PermissionRequest), adding the rewrite* helpers.
Parameter Attributes
_raw: dict[str, Any]ctx: HookContext
Methods
| Name | Description |
|---|---|
| rewrite() |
Allow the tool but replace its input with updated_input (Claude Code’s updatedInput).
|
| rewrite_command() | Rewrite a Bash command in place, replacing command while preserving the rest of the tool input. |
| rewrite_content() |
Rewrite each editable-content field through transform, allowing the tool with the result.
|
rewrite()
Allow the tool but replace its input with updated_input (Claude Code’s updatedInput).
Usage
rewrite(updated_input, *, note=None)updated_input must satisfy the same tool’s schema; note surfaces as additionalContext so the model sees that its input was rewritten.
rewrite_command()
Rewrite a Bash command in place, replacing command while preserving the rest of the tool input.
Usage
rewrite_command(new_command, *, note=None)rewrite_content()
Rewrite each editable-content field through transform, allowing the tool with the result.
Usage
rewrite_content(transform, *, note=None)Maps the transform onto whichever field holds new source for this tool — an Edit’s new_string, a Write’s content, a NotebookEdit’s new_source, or every span of a MultiEdit’s edits — and preserves the rest of the input. Returns None (a no-op) when transform leaves every field unchanged, so callers stay idempotent.
PreToolUseEvent
Fires before a tool is executed. Return a block result to prevent execution.
Usage
PreToolUseEvent(_raw, ctx)Parameter Attributes
_raw: dict[str, Any]ctx: HookContext
PermissionRequestEvent
Fires when a permission dialog would be shown.
Usage
PermissionRequestEvent(_raw, ctx)allow/block/rewrite answer the dialog (block maps to a deny with the message shown to the user); None and warn fall through, so the dialog shows normally.
Parameter Attributes
_raw: dict[str, Any]ctx: HookContext
PostToolUseEvent
Fires after a tool completes successfully, with access to the tool response.
Usage
PostToolUseEvent(_raw, ctx)Parameter Attributes
_raw: dict[str, Any]ctx: HookContext
PostToolUseFailureEvent
Fires after a tool fails, providing the error message and interrupt status.
Usage
PostToolUseFailureEvent(_raw, ctx)Parameter Attributes
_raw: dict[str, Any]ctx: HookContext
UserPromptSubmitEvent
Fires when the user submits a prompt, before the agent processes it.
Usage
UserPromptSubmitEvent(_raw, ctx)Parameter Attributes
_raw: dict[str, Any]ctx: HookContext
StopEvent
Fires when the agent is about to stop. Return a block result to prevent stopping.
Usage
StopEvent(_raw, ctx)Parameter Attributes
_raw: dict[str, Any]ctx: HookContext
SubagentStartEvent
Fires when a subagent is launched. Provides agent_type for filtering.
Usage
SubagentStartEvent(_raw, ctx)Parameter Attributes
_raw: dict[str, Any]ctx: HookContext
SubagentStopEvent
Fires when a subagent finishes. Provides agent_type for filtering.
Usage
SubagentStopEvent(_raw, ctx)Parameter Attributes
_raw: dict[str, Any]ctx: HookContext
BackgroundTask
A background task keeping the session alive, from a Stop/SubagentStop payload.
Usage
BackgroundTask(id, type, status, description, raw)Since Claude Code v2.1.145 a stopping session reports the work still running so a hook can tell “the session is done” from “the session is paused waiting for background work to wake it back up”. Each task carries the common fields below plus type-specific extras — a shell task’s command, a subagent’s agent_type, an MCP task’s server and tool — preserved in raw.
Attributes
id: str-
The task’s stable identifier.
type: str-
The task kind, one of
shell,subagent,monitor, workflow,teammate,cloud session, orMCP task. status: str-
The task’s reported lifecycle status.
description: str-
A human-readable summary of the task.
raw: Mapping[str, Any]- The full task mapping, including the type-specific fields.
SessionCron
A scheduled prompt registered on the session, from a Stop/SubagentStop payload.
Usage
SessionCron(id, schedule, recurring, prompt)A cron keeps a session alive to re-run prompt on schedule; its presence means the session is paused for future scheduled work rather than finished.
Attributes
id: str-
The cron’s stable identifier.
schedule: str-
The cron schedule expression.
recurring: bool-
Whether the cron fires repeatedly rather than once.
prompt: str- The prompt replayed to the agent when the cron fires.
PreCompactEvent
Fires before context compaction, providing the trigger and custom instructions.
Usage
PreCompactEvent(_raw, ctx)Parameter Attributes
_raw: dict[str, Any]ctx: HookContext
NotificationEvent
Fires on system notifications, providing message, title, and notification type.
Usage
NotificationEvent(_raw, ctx)Parameter Attributes
_raw: dict[str, Any]ctx: HookContext
SessionStartEvent
Fires when a session starts, providing what triggered it. Warns inject context; it cannot block.
Usage
SessionStartEvent(_raw, ctx)source is one of "startup", "resume", "clear", or "compact".
Parameter Attributes
_raw: dict[str, Any]ctx: HookContext
SessionEndEvent
Fires when the session ends, providing the termination reason. Output is ignored, so it cannot block.
Usage
SessionEndEvent(_raw, ctx)Parameter Attributes
_raw: dict[str, Any]ctx: HookContext
HookContext
Runtime context injected into every hook event.
Usage
HookContext(session, transcript, settings, project_root=None)Holds session state, the transcript Session, settings, and LLM/CLI helpers.
Parameter Attributes
session: SessionStoretranscript: Sessionsettings: HooksSettings | Noneproject_root: Path | None = None
Attributes
| Name | Description |
|---|---|
| c |
Alias for settings (shortest form).
|
| conf |
Alias for settings.
|
| prior | The session window before the current turn’s last exchange (cached). |
| s |
Alias for session.
|
| state |
Alias for session.
|
| t |
Alias for transcript.
|
| turn | The one-turn view of the current turn (cached), with prompt matching via matches. |
c
Alias for settings (shortest form).
c: HooksSettings | None
conf
Alias for settings.
conf: HooksSettings | None
prior
The session window before the current turn’s last exchange (cached).
prior: Session
s
Alias for session.
s: SessionStore
state
Alias for session.
state: SessionStore
t
Alias for transcript.
t: Session
turn
The one-turn view of the current turn (cached), with prompt matching via matches.
turn: Turn
Methods
| Name | Description |
|---|---|
| diff() |
A compact diff via ccx vcs diff when available, else plain git.
|
| nlp() |
Whether text matches any pattern — the escape hatch for matching arbitrary prose.
|
| transcript_block() |
The rendered transcript wrapped in a <transcript> tag carrying its source path.
|
| transcript_text() | The transcript rendered turn by turn under the default budget. |
diff()
A compact diff via ccx vcs diff when available, else plain git.
Usage
diff(source="uncommitted", *, commit=None, scope=None, budget=4000)Prefers cc-context’s token-budgeted ccx vcs diff and falls back to git when ccx is absent or failing, so a hook gets a real diff in any repo. The git fallback is bounded to roughly budget tokens with a trailing marker when truncated, so a large diff can’t blow the caller’s context.
Parameters
source: str = "uncommitted"-
"uncommitted"(the default),"staged", or any git ref. Ignored whencommitis set. commit: str | None = None-
When set, the diff introduced by this commit (root-commit-safe), overriding source; its git fallback is
git show --stat -p, so it carries the commit header + diffstat ahead of the patch. scope: str | None = None-
Restrict the diff to this path.
budget: int = 4000-
Token budget for both the
ccxoutput and the bounded git fallback.
nlp()
Whether text matches any pattern — the escape hatch for matching arbitrary prose.
Usage
nlp(text, *patterns)A string pattern is a case-insensitive regex; a ~captain_hook.Clause runs the dependency-clause scan. For the current turn’s prompt, prefer evt.ctx.turn.matches(*patterns).
Example
>>> evt.ctx.nlp(evt.ctx.t.assistant_text(), Clause(noun=Phrase("test"), verb=Phrase("skip")))transcript_block()
The rendered transcript wrapped in a <transcript> tag carrying its source path.
Usage
transcript_block(*, window=RECENT_WINDOW)Defaults to a recent-event window rather than the whole session. The render clips long turns and tool calls under Budget, so an agent-mode LLM uses the path to read the untruncated content (e.g. a full ExitPlanMode plan) or earlier history.
Parameters
window: int | None = RECENT_WINDOW-
Render only the most recent
windowevents;Nonerenders the whole session.
transcript_text()
The transcript rendered turn by turn under the default budget.
Usage
transcript_text(*, window=None)Parameters
window: int | None = None-
Render only the most recent
windowevents;Nonerenders the whole session.
Turn
The current turn as a one-turn ~cc_transcript.query.Session view.
Usage
Turn(turns, path=None, attachments=())What evt.ctx.turn returns: the full windowed transcript surface (user_text, has_tool, tool_calls, …) plus prompt matching, so hooks can test the turn’s opening prompt without importing the NLP machinery.
Methods
| Name | Description |
|---|---|
| matches() | Whether the turn’s opening prompt matches any pattern. |
matches()
Whether the turn’s opening prompt matches any pattern.
Usage
matches(*patterns)A string pattern is a case-insensitive regex; a ~captain_hook.Clause runs the dependency-clause scan.
Example
>>> evt.ctx.turn.matches(Clause(noun=Phrase("work"), verb=Phrase("stop", "halt")))HookResult
The return value from a hook handler, specifying the action and optional message.
Usage
HookResult(
*, action, message=None, updated_input=None, note=None, approve=True
)updated_input and note carry the rewrite action’s replacement tool input and the advisory context surfaced alongside it. approve gates the PreToolUse permissionDecision: allow rider a warn normally carries: False surfaces the message as pure additionalContext without pre-approving the tool (the evt.context variant).
Parameter Attributes
action: Actionmessage: str | None = Noneupdated_input: dict[str, Any] | None = Nonenote: str | None = Noneapprove: bool = True
Methods
| Name | Description |
|---|---|
| of() |
Build a HookResult, dedenting and stripping message for readable triple-quoted handler returns.
|
of()
Build a HookResult, dedenting and stripping message for readable triple-quoted handler returns.
Usage
of(action, message=None)Action
Hook result action determining how the hook output is handled.
Usage
Action()Agent
Condition matching the current event’s subagent type against one or more names.
Usage
Agent(*names)Exact membership (not a regex). Pass names variadically, or as a single |-joined string for back-compat (Agent("Explore|claude-code-guide") is Agent("Explore", "claude-code-guide")).
Parameter Attributes
names: str = ()