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. |
| 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 Nonebackground_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
as_input(call_type)block()
Block with message; with confirm, only once a small model confirms the match.
Usage
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.Confirmbefore 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
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
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
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 | 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 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
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
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
MessageDisplayEvent
Fires once per streamed chunk of an assistant message; a rewrite replaces the chunk’s displayed text.
Usage
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
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
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,
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: SessionStoretranscript: Session | RemoteSession | LazyTranscriptsettings: HooksSettings | Noneproject_root: Path | None = Noneroot_transcript: Session | RemoteSession | LazyTranscript | None = Noneroot_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.
s: SessionStore
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
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 = {}) -> strattempts 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
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
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_modelneeds free text Jev cannot give.
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")))root_excerpt()
The root session’s events that mention any of needles, with around events either side,
Usage
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
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
root_transcript_block(*, window=RECENT_WINDOW, tool_results=False, budget=None)transcript_text()
The transcript rendered turn by turn under budget.
Usage
transcript_text(*, window=None, tool_results=False, budget=None)Parameters
window: int | None = None-
Render only the span covering the most recent
windowmessages;Noneis the whole session. tool_results: bool = False-
Render each tool result after its call, as
result:orfailed:. budget: Budget | None = None-
Character budgets for prose, tool calls and answer previews;
Noneis cc-transcript’s default.
Model
The model a session runs on, parsed from the id its transcript records.
Usage
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: strfamily: str | Noneversion: 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
at_least(family, major, minor=0)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,
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
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 = ()