Skip to main content

Hook system

Use lifecycle hooks to observe agent activity, block an action, or rewrite a prompt, tool call, or result. Operators deploy hooks, and the model cannot change or disable them. For operator configuration, see Permissions and posture.

Hook phases

PhaseWhen it firesCan block?Can mutate?Mutation target
SessionStartBefore the first prompt is recordedYes, aborts the runNo
UserPromptSubmitAfter command expansion, before recordingYes, ends the runYesPrompt text
PreToolUseAfter permission approval, before executionYes, returns an error instead of runningYesTool arguments
PostToolUseAfter execution, before emitting the resultNoYesTool result
StopWhen a run endsNoNo
SubagentStopWhen a child run endsNoNo
TeammateIdleWhen a team member becomes idleNoNo
TaskCreatedWhen the team supervisor creates a taskNoNo
TaskCompletedWhen a team member completes a taskNoNo

An execution error in SessionStart or UserPromptSubmit ends the run. PreToolUse and PostToolUse errors become annotations and do not abort it.

Shell hook contract

Each hook is a shell command run as <shell> -c <command> (default /bin/sh). The JSON HookEvent is written to the process's stdin:

{
"Phase": "PreToolUse",
"Tool": "Shell",
"Input": { "command": "rm -rf build" },
"SessionID": "8867bdea940108c1dd82d13d3fb7fc61"
}

Input is phase-specific. For PreToolUse it is the tool's raw arguments JSON. For PostToolUse it is {"content": "...", "is_error": false}. For UserPromptSubmit it is {"prompt": "..."}.

Exit codes

Exit codeOutcome
0Allow. Stdout can contain a message or mutation envelope.
2Block. Mecatl reads the reason from stdout, then stderr.
Any other valueReport a hook error. The phase determines whether the run aborts.

A single invocation is bounded by a 30-second timeout.

Mutation envelope

On exit 0, if stdout is a JSON object, it is parsed as a control envelope:

{
"mutated": <phase-specific payload>,
"message": "optional human-readable note"
}

mutated must have the same shape as Input for that phase. For PreToolUse it replaces the tool's arguments before execution. For PostToolUse it replaces the result the model and client see. For UserPromptSubmit it replaces the recorded prompt text.

A non-object stdout value is treated as a message, and the original payload is unchanged.

Block example: guard Shell against rm -rf

#!/bin/sh
# Use this script as a PreToolUse hook for Shell.
event="$(cat)"
if printf '%s' "$event" | grep -q '"rm -rf'; then
echo "blocked: 'rm -rf' is not permitted by policy"
exit 2
fi
exit 0

Exit 2 returns an error ToolResult without running the command.

Mutation examples

Rewrite the prompt before it is recorded

This UserPromptSubmit hook removes an API key pattern before the prompt reaches the model or session store:

#!/bin/sh
event="$(cat)"
prompt="$(printf '%s' "$event" | python3 -c "import sys,json; print(json.load(sys.stdin)['Input']['prompt'])")"
clean="$(printf '%s' "$prompt" | sed 's/sk-[A-Za-z0-9]\{32,\}/[REDACTED]/g')"
printf '%s' "$clean" | \
python3 -c "import json,sys; print(json.dumps({'mutated': {'prompt': sys.stdin.read()}}))"
exit 0

Mecatl records and sends the mutated prompt.

Redact a secret from a tool result

This PostToolUse hook removes AWS credentials from shell output:

#!/bin/sh
event="$(cat)"
content="$(printf '%s' "$event" | python3 -c "import sys,json; print(json.load(sys.stdin)['Input']['content'])")"
clean="$(printf '%s' "$content" | sed 's/AKIA[A-Z0-9]\{16\}/[REDACTED_KEY]/g')"
printf '%s' "$clean" | python3 -c "
import json, sys
content = sys.stdin.read()
print(json.dumps({'mutated': {'content': content, 'is_error': False}}))
"
exit 0

The client stream and model history both receive the redacted result.

Permission policy evaluates original args

For PreToolUse, the permission policy evaluates the original arguments. It does not evaluate rewritten arguments again because operator hooks are trusted. A hook can therefore widen a call beyond what the original permission decision covered. Keep security controls in the permission policy when a hook must not bypass them.

Guardrails: a built-in model-backed hook

Mecatl also includes model-backed PreToolUse and PostToolUse hooks for content that scripts cannot reliably classify, such as prompt injection in a fetched page or possible secret exfiltration in tool arguments. These guardrails remain off until you configure a checker model. See Permissions and guardrails for the default matchers, enforcement modes, and approval flow.

What's next