Events & Results

Event

Hook lifecycle events that can trigger registered hooks.

Usage

Source

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

Source

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

Source

as_input(call_type)
context()

Emit advisory context exactly like warn(), but without pre-approving the tool.

Usage

Source

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

Source

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 | None

The 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

Source

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

Source

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

None for non-edit tools, files without a supported grammar, and Writes off PreToolUse (their pre-image is unknowable once the write lands — the same rule as replaced). The pre side is replaced; the post side is content.

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 oldnew 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

Source

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

Source

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

Source

rewrite_command(new_command, *, note=None)
rewrite_content()

Rewrite each editable-content field through transform, allowing the tool with the result.

Usage

Source

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

Source

PreToolUseEvent(_raw, ctx)

Parameter Attributes

_raw: dict[str, Any]
ctx: HookContext

PermissionRequestEvent

Fires when a permission dialog would be shown.

Usage

Source

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

Source

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

Source

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

Source

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

Source

StopEvent(_raw, ctx)

Parameter Attributes

_raw: dict[str, Any]
ctx: HookContext

SubagentStartEvent

Fires when a subagent is launched. Provides agent_type for filtering.

Usage

Source

SubagentStartEvent(_raw, ctx)

Parameter Attributes

_raw: dict[str, Any]
ctx: HookContext

SubagentStopEvent

Fires when a subagent finishes. Provides agent_type for filtering.

Usage

Source

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

Source

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, or MCP 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

Source

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

Source

PreCompactEvent(_raw, ctx)

Parameter Attributes

_raw: dict[str, Any]
ctx: HookContext

NotificationEvent

Fires on system notifications, providing message, title, and notification type.

Usage

Source

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

Source

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

Source

SessionEndEvent(_raw, ctx)

Parameter Attributes

_raw: dict[str, Any]
ctx: HookContext

HookContext

Runtime context injected into every hook event.

Usage

Source

HookContext(session, transcript, settings, project_root=None)

Holds session state, the transcript Session, settings, and LLM/CLI helpers.

Parameter Attributes

session: SessionStore
transcript: Session
settings: HooksSettings | None
project_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.

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

Source

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 when commit is 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 ccx output and the bounded git fallback.
nlp()

Whether text matches any pattern — the escape hatch for matching arbitrary prose.

Usage

Source

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

Source

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 window events; None renders the whole session.
transcript_text()

The transcript rendered turn by turn under the default budget.

Usage

Source

transcript_text(*, window=None)
Parameters
window: int | None = None
Render only the most recent window events; None renders the whole session.

Turn

The current turn as a one-turn ~cc_transcript.query.Session view.

Usage

Source

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

Source

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

Source

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: Action
message: str | None = None
updated_input: dict[str, Any] | None = None
note: str | None = None
approve: 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

Source

of(action, message=None)

Action

Hook result action determining how the hook output is handled.

Usage

Source

Action()
  • block: Prevents the tool use or stops the agent.
  • warn: Adds advisory context without blocking.
  • allow: Explicitly permits the action.
  • rewrite: Replaces a PreToolUse tool’s input with updated_input and allows it.

Agent

Condition matching the current event’s subagent type against one or more names.

Usage

Source

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 = ()