Documentation v0.3.2
Earshot / Extending

Hooks and subagents

Add context, restrict actions, and delegate within the parent contract.

Reviewed for 0.3.2 · Latest documentation

Hooks#

Claude Code's contract, so hooks you already have keep working: the same settings shape, the same JSON on stdin, the same exit code 2 convention.

{
  "hooks": {
    "PreToolUse": [
      { "matcher": "write|edit", "hooks": [{ "type": "command", "command": "./scripts/guard.sh", "timeout": 10 }] }
    ]
  }
}
EventMay
SessionStartAdd context to the session
UserPromptSubmitBlock the prompt, or add context to it
PreToolUseBlock a call, or turn an allow into a prompt
PostToolUseAdd context to the result
StopAsk for one more model call — once per turn
SessionEndObserve

A hook may never approve. "decision": "approve" and permissionDecision: "allow" are read, reported and ignored. A hook command lives in a settings file — including a project's checked-in one — so a hook that could approve would be a repository granting itself permissions you never gave. Hooks make the answer stricter or leave it alone. This is the one deliberate incompatibility with Claude Code.

PostToolUse cannot block, because the tool has already run and saying otherwise would tell the model something untrue. Its output is appended to the result, never substituted for it.

Failure is not obedience. A hook that times out is killed and blocks nothing. One that exits non-zero for any reason other than 2 is reported and blocks nothing. One that prints something unparseable has it treated as a note. All of it is shown to you — a hook failing quietly is one you go on believing protects you.

Hooks from every scope are concatenated rather than overriding one another, so a project cannot remove one you set globally.

Subagents#

The task tool runs a nested agent with its own context window and returns its answer, not its transcript — so a search across a large codebase costs the parent one paragraph instead of forty tool results.

What it inherits:

Permission mode and rulesYes — nothing it does escapes the gate
The declared scopeYes, the same contract object; a file nobody listed still prompts
CostYes, onto the session's total. A budget a subagent could spend outside is not a budget
An approved planYes
ContextNo — that is the point
The session transcriptNo; its messages are not yours
A task tool of its ownNo; nesting stops at one level

Its tool list is intersected with the parent's and defaults to the read-only tools. Starting one is itself gated, so Task(...) rules work on it. A subagent that ran out of steps says so rather than passing off a partial answer.

Something unclear?Suggest a correction on GitHub ↗