Skip to main content
Hooks let a worker run your own commands at defined moments in the agentic loop — before a tool runs, after it runs, or when the turn stops. Use them to validate input, auto-format output, block risky actions, or log activity for review, without changing the worker’s instructions.

What a hook is

A hook is a small command — typically a shell command or script — that the worker runs automatically at a specific moment, instead of something you trigger by hand. You author hooks once, as a worker file, and every task that worker runs picks them up. Hooks live in a hooks.json file at the worker’s file scope, alongside its Skills, worker instructions, and memory. Because they’re worker files, you can edit them yourself through the Files panel, or ask the worker to write them for you.
This is the deliberate exception to how a worker’s definition otherwise works: a running task pins the worker’s instructions, model, and connections as a snapshot for its whole lifetime, but hooks are read live at the start of every turn instead. If you fix a broken hook mid-task, the very next turn picks up the fix. A worker-less task has no worker file scope, so it has no hooks.

When hooks fire

Hooks attach to named events in the loop. Each event fires at a precise point, and each has a different amount of power over what happens next. A hook only reacts to the events and tools it’s configured to match — you point it at a tool name (or a pattern covering several) so it only runs where it’s relevant, for example only on bash and write_file calls, or every web_* tool.
Every task gets its own isolated sandbox automatically, and each hook command runs inside it with a timeout, so a slow or hanging hook can’t stall the turn indefinitely.

Configuring hooks

Hooks are defined in a hooks.json file, grouped by event. Each entry pairs a matcher (which tool names it applies to) with one or more commands to run:
hooks.json
The matcher above ("bash|write_file") limits that hook to two tools; an omitted matcher, like the Stop entry, matches everything for that event. Each hook command receives the details of the call — which tool, what arguments, and (for PostToolUse) the result — as JSON on standard input, and has to answer within its timeout (30 seconds by default, capped at 60) before the turn moves on.
A worker’s hooks are capped to keep them lightweight: up to 8 matchers per event and 4 commands per matcher, with the whole file limited to 16 KB. If hooks.json is malformed, it’s ignored — with a warning — rather than breaking the task.

What hooks are for

Validate input

Run a PreToolUse hook to check a tool’s arguments before they run — for example, rejecting a shell command that touches a protected path or a file write outside an approved directory.

Auto-format output

Run a PostToolUse hook that reformats or annotates a result — for example, running a linter or formatter after a file edit and feeding the outcome back to the worker.

Enforce guardrails

Use PreToolUse hooks as a second layer of policy on top of tool approvals — an org-specific rule a worker should never be allowed to break, regardless of what the model decides.

Audit and review

Use Stop or TaskCompleted hooks to log activity, or to kick off a review pass — for example, notifying an improvement-reviewer once a task settles, feeding self-improvement.

How a hook responds

A hook command reports back with an exit code:
  • Success — the call proceeds normally. The hook can still attach extra context for PostToolUse.
  • Block — for PreToolUse, the call is denied and the reason is shown to the worker as the tool’s result, so it can adjust course; for PostToolUse and Stop, the call already happened and the reason is appended as context.
  • Anything else — treated as a non-blocking warning. The turn continues, and the warning is recorded so you can see it happened.
Blocked calls are never silent — they show up in the task transcript as a failed tool result with the hook’s reason, so you can always see why the worker didn’t do something.

Hooks and human-in-the-loop

Hooks and approvals solve different problems, and it helps to keep them separate:
  • Human-in-the-loop approvals decide whether a call is allowed to happen at all — a person (or the permission classifier) says yes or no before execution.
  • Hooks run after that decision, once a call has already been approved. A hook can only add a further restriction — it can block a call that was allowed, but it can never approve, skip, or auto-answer an approval on its own.
Think of approvals as the gate, and hooks as a checkpoint just past it: hooks tighten what an already-permitted worker can do, they don’t loosen it.

Next steps

The agentic loop

See where PreToolUse, PostToolUse, and Stop fit in a turn.

Human-in-the-loop

Understand approvals, the permission classifier, and decision memory.

Self-improvement & learning

Learn how a Stop hook can feed a worker’s review-and-learn loop.

Skills

See the other worker files that live alongside hooks.json.