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 }] }
]
}
}| Event | May |
|---|---|
SessionStart | Add context to the session |
UserPromptSubmit | Block the prompt, or add context to it |
PreToolUse | Block a call, or turn an allow into a prompt |
PostToolUse | Add context to the result |
Stop | Ask for one more model call — once per turn |
SessionEnd | Observe |
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 rules | Yes — nothing it does escapes the gate |
| The declared scope | Yes, the same contract object; a file nobody listed still prompts |
| Cost | Yes, onto the session's total. A budget a subagent could spend outside is not a budget |
| An approved plan | Yes |
| Context | No — that is the point |
| The session transcript | No; its messages are not yours |
A task tool of its own | No; 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.