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.
annotations The ccx: annotations this event carries, parsed once: each key mapped to its value, or None for a flag.
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.
disallowed_tools The tools the session’s claude process was launched without.
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.
tool_use_id The id of the tool call this event concerns; None on events about no call.
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.

annotations

The ccx: annotations this event carries, parsed once: each key mapped to its value, or None for a flag.

annotations: Annotations

Read from a ccx:<key>[=<value>] token in a real comment of the Bash command (nested sh -c and eval payloads included, quoted strings and heredoc bodies never), from a whole ccx: <key>[=<value>] ... line in an Agent or Task prompt or in Skill args, and from CAPT_HOOK_CCX_RAW set to 1, true, or yes, which contributes raw. Match it in skip_if/only_if with ~captain_hook.Annotated.

Example
>>> if "raw" in evt.annotations:
...     return None
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).

disallowed_tools

The tools the session’s claude process was launched without.

disallowed_tools: frozenset[str]

Walks the process tree to the nearest claude ancestor and collects the tool names it was started with under --disallowedTools (or --disallowed-tools); a hook reads it to stay silent when the tool it would direct the agent to does not exist.

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. A team lead reads its team’s list (~captain_hook.tasks.Tasks.list_id()); a session with no store has no tasks.

tool_use_id

The id of the tool call this event concerns; None on events about no call.

tool_use_id: str | None

Methods

Name Description
as_input() Return input narrowed to call_type, or None when it is a different tool call.
block() Block with message; with confirm, only once a small model confirms the match.
context() Emit advisory context exactly like warn(), but without pre-approving the tool.
decide() Ask a fast typed classifier about this event and return its answers, or None when it fails.
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)
block()

Block with message; with confirm, only once a small model confirms the match.

Usage

Source

block(message, *, confirm=None, system_message=None)
Parameters
message: str

The rule the call breaks, then the remediation.

confirm: Confirm | None = None

Send the block through ~captain_hook.Confirm before it lands; the call is allowed with a one-line note when the model times out, fails, is unsure, or finds no match.

system_message: str | None = None
Text shown to the user as Claude Code’s systemMessage.
context()

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

Usage

Source

context(*parts, system_message=None)

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.

decide()

Ask a fast typed classifier about this event and return its answers, or None when it fails.

Usage

Source

decide(state, questions, *, provider="jev", timeout=3.0)

For questions that need no tools and no transcript reading of their own: one HTTP call to TypeSafe Jev (provider="jev") or OpenAI Decisions (provider="openai") answers every Binary, Label, and Score question in questions about state in about 120 ms. timeout caps the call, retries included, and the request’s own deadline cuts it shorter. A timeout, a provider error, or a missing key logs a warning and returns None, so the hook falls through as it would with no verdict; a rejected request or a missing key also records a fault the next session start reports. A question the provider declines answers Refused. Decide on facts the text states; never let a decision about text the agent wrote authorize anything.

Inline tests stub the call with Input(decide={...}), so a test never reaches the network.

Example
>>> from spawnllm import Binary, BinaryAnswer
>>> asked = {"rollback": Binary("Does the user ask to roll back a release?")}
>>> match (decision := evt.decide(evt.user_prompt or "", asked)) and decision.answers["rollback"]:
...     case BinaryAnswer(p_yes=p_yes) if p_yes > 0.9:
...         return evt.warn("Roll back with `ci release rollback`.")
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, tool_results: bool = False, budget: Budget | None = None, diff: bool | str = False, backend: TJudge = 'jev') -> 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, tool_results: bool = False, budget: Budget | None = None, diff: bool | str = False, backend: TJudge = 'jev') -> 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, tool_results: bool = False, budget: Budget | None = None, diff: bool | str = False, backend: TJudge = 'jev') -> 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, tool_results: bool = False, budget: Budget | None = None, diff: bool | str = False, backend: TJudge = 'jev', escalate: Callable[[M], bool] | None = None) -> 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 bool answer, or a model whose every field is categorical (see ~captain_hook.primitives.llm.verdict_questions()), goes to TypeSafe Jev instead of the LLM unless backend="llm"; a str reply, an int, or any other model always asks the LLM. escalate judges a model in two stages, as [llm_evaluate][captain_hook.llm_evaluate] does: Jev decides its categorical fields, and only a verdict escalate returns true for goes on to the LLM for the rest.

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, and so does a Jev call that times out or errors. A question Jev refuses returns None.

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, system_message=None)

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.

system_message: str | None = None
Text shown to the user as Claude Code’s systemMessage.
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 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

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

MessageDisplayEvent

Fires once per streamed chunk of an assistant message; a rewrite replaces the chunk’s displayed text.

Usage

Source

MessageDisplayEvent(_raw, ctx)

message_id groups the chunks of one message, index orders them from 0, final marks the last chunk, and delta is this chunk’s text fragment, not the cumulative message.

Parameter Attributes

_raw: dict[str, Any]
ctx: HookContext

ResourcePressureEvent

Fired by the capt-hookd host, not Claude Code, when a session’s child process stays resource-heavy.

Usage

Source

ResourcePressureEvent(_raw, ctx)

stage is "warn" on the first sustained sample, "judge" once the grace period lapses with the child still heavy, and "escalate" when the child outlives a SIGTERM. process carries the child’s identity (pid, start_unix, argv, cwd) and usage; metrics marks which usage readings were available. Only the warn stage’s reply is read: a warn or allow acknowledges it, and any other result makes the host stop tracking the child. The judge and escalate replies are not read.

The host tracks the child by its pid and sub-second start time at every tick and reads the argv vector it sends fresh at each stage dispatch. The worker verifies again right before a signal with what ps exposes: the parent and group ids, the whole-second start time, the working directory, and the whitespace-normalized joined command text, which is not the argv vector, so two argument vectors that join to the same text compare equal there.

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,
    root_transcript=None,
    root_path=None
)

Holds session state, the transcript Session, settings, and LLM/CLI helpers. Inside a subagent or teammate lane, or a Claude session on a live Orca dispatch, transcript is the lane’s own and root_transcript tails the session that spawned or dispatched it, where the user’s own words live; it is None everywhere else.

Parameter Attributes

session: SessionStore
transcript: Session | RemoteSession | LazyTranscript
settings: HooksSettings | None
project_root: Path | None = None
root_transcript: Session | RemoteSession | LazyTranscript | None = None
root_path: Path | None = None

Attributes

Name Description
c Alias for settings (shortest form).
conf Alias for settings.
model The session’s model, from the transcript’s latest assistant reply; None before the first.
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

model

The session’s model, from the transcript’s latest assistant reply; None before the first.

model: Model | None

prior

The session window before the current turn’s last exchange (cached).

prior: Session | RemoteSession

s

Alias for session.

state

Alias for session.

state: SessionStore

t

Alias for transcript.

t: Session | RemoteSession

turn

The one-turn view of the current turn (cached), with prompt matching via matches.

turn: Turn | RemoteSession

Methods

Name Description
call_llm() Ask the selected backend once the request’s deadline still has room, clamping the call’s timeout to it.
decide() Ask a decision provider once the request’s deadline still has room, clamping timeout to it.
decide_verdict() Ask TypeSafe Jev for the categorical fields of response_model, or None when it refuses one.
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.
root_excerpt() The root session’s events that mention any of needles, with around events either side,
root_excerpt_block() The root excerpt for needles rendered as <root_excerpt>; empty outside a lane or with no match.
root_transcript_block() The lane’s root session rendered like transcript_block as <root_transcript>; empty outside a lane.
transcript_text() The transcript rendered turn by turn under budget.
call_llm()

Ask the selected backend once the request’s deadline still has room, clamping the call’s timeout to it.

Usage

Source

call_llm(self, template: str | Prompt, args: Any = (), specialty: TSpecialty = 'review', model: TModel = 'small', timeout: int = 180, transcript: bool | int | Literal['recent', 'full'] = False, tool_results: bool = False, budget: Budget | None = None, diff: bool | str = False, agent: bool = False, backend: LlmBackend | None = None, attempts: int | None = None, tools: tuple[str, ...] | None = None, evidence: bool = True, response_model: type[M], kwargs: Any = {}) -> M
call_llm(self, template: str | Prompt, args: Any = (), specialty: TSpecialty = 'review', model: TModel = 'small', timeout: int = 180, transcript: bool | int | Literal['recent', 'full'] = False, tool_results: bool = False, budget: Budget | None = None, diff: bool | str = False, agent: bool = False, backend: LlmBackend | None = None, attempts: int | None = None, tools: tuple[str, ...] | None = None, evidence: bool = True, response_model: None = None, kwargs: Any = {}) -> str

attempts caps the provider attempts spawnllm makes for one call and tools names the built-in tools the model may use (() for none); either one routes the call through a spawnllm.RunSpec of its own, and None keeps spawnllm’s defaults. A deadline already passed once the backend is selected raises TimeoutError without a provider call. evidence=False records no transcript evidence for the call, so the session transcript is never resolved: the lane for a prompt built from no transcript at all, such as a host event’s.

decide()

Ask a decision provider once the request’s deadline still has room, clamping timeout to it.

Usage

Source

decide(state, questions, *, provider, timeout)

The call ends DECIDE_MARGIN_SECONDS before the caller deadline, retries included, and raises TimeoutError without a request once that leaves no time. An API actor passes the key it captured; on a host, spawnllm reads the provider’s variable or its macOS Keychain item.

decide_verdict()

Ask TypeSafe Jev for the categorical fields of response_model, or None when it refuses one.

Usage

Source

decide_verdict(
    instructions,
    state,
    response_model,
    *,
    root,
    evidence=True,
    defaults=False
)

~captain_hook.primitives.llm.verdict_questions() turns each field into one question under instructions, and ~captain_hook.primitives.llm.verdict_from() maps the answers back. A rejected request or a missing key records a fault under root and raises, as a timeout does. evidence=False keeps the transcript open after the call, as in call_llm(). defaults=True keeps the default of every field Jev cannot answer, as a two-stage call’s first stage.

Raises
TypeError
When a field of response_model needs free text Jev cannot give.
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")))
root_excerpt()

The root session’s events that mention any of needles, with around events either side,

Usage

Source

root_excerpt(needles, *, around=2)

from the last 16 MiB of its transcript rather than its event tail; None outside a lane.

root_excerpt_block()

The root excerpt for needles rendered as <root_excerpt>; empty outside a lane or with no match.

Usage

Source

root_excerpt_block(needles, *, tool_results=False, budget=None)
root_transcript_block()

The lane’s root session rendered like transcript_block as <root_transcript>; empty outside a lane.

Usage

Source

root_transcript_block(*, window=RECENT_WINDOW, tool_results=False, budget=None)
transcript_text()

The transcript rendered turn by turn under budget.

Usage

Source

transcript_text(*, window=None, tool_results=False, budget=None)
Parameters
window: int | None = None

Render only the span covering the most recent window messages; None is the whole session.

tool_results: bool = False

Render each tool result after its call, as result: or failed:.

budget: Budget | None = None
Character budgets for prose, tool calls and answer previews; None is cc-transcript’s default.

Model

The model a session runs on, parsed from the id its transcript records.

Usage

Source

Model(id, family, version)

family and version are set for a claude-<family>-<major>[-<minor>] id, such as claude-opus-5-5 or claude-sonnet-4-5-20250929, and None for any other id.

Parameter Attributes

id: str
family: str | None
version: tuple[int, int] | None

Example

>>> ctx.model is not None and ctx.model.at_least("opus", 5, 5)

Methods

Name Description
at_least() Whether this is family at version major.minor or later.
at_least()

Whether this is family at version major.minor or later.

Usage

Source

at_least(family, major, minor=0)

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,
    system_message=None,
    confirm=None
)

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). system_message is shown to the user as Claude Code’s top-level systemMessage alongside whatever the action renders, including a Stop that allows; PreCompact, whose stdout is plain text, drops it. confirm sends a block through a small model before it lands (evt.block(..., confirm=Confirm(...))).

Parameter Attributes

action: Action
message: str | None = None
updated_input: dict[str, Any] | None = None
note: str | None = None
approve: bool = True
system_message: str | None = None
confirm: Confirm | None = None

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, or a MessageDisplay chunk’s displayed text with message.

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