Hooks
Hooks run at points in the agent lifecycle: shell commands, or prompts a model answers. The format matches Claude Code's, so existing hooks work. Matchers are case-insensitive, so Bash matches Larik's bash.
{
"hooks": {
"PreToolUse": [
{
"matcher": "bash",
"hooks": [
{ "type": "command", "command": "./scripts/guard.sh", "timeout": 10 }
]
}
],
"PostToolUse": [
{
"matcher": "write|edit",
"hooks": [
{ "type": "command", "command": "gofmt -l . >&2 && exit 2 || true" }
]
}
],
"Stop": [
{
"hooks": [
{ "type": "command", "command": "./scripts/require-tests.sh" }
]
}
]
}
}
| Event | Fires | Can do |
|---|---|---|
SessionStart |
first turn of each fresh context (matcher: startup, resume, clear) |
Add context. Stdout goes to the model. |
UserPromptSubmit |
before a prompt is sent | Block it, or add context (stdout) |
PreToolUse |
before the permission check (matcher: tool name) | allow (skips the prompt), deny, ask, or rewrite the input with updatedInput |
PostToolUse |
after a tool runs | Send feedback to the model |
Stop / SubagentStop |
when the agent (or a subagent) would end its turn | decision: "block" makes it continue with your reason (at most 5 times; stop_hook_active is set) |
PreCompact, Notification, SessionEnd |
compaction, permission prompts, exit | Observe only |
How a hook's result is read:
- Stdin is a JSON payload with
session_id,transcript_path,cwd,hook_event_name,tool_name,tool_input,tool_response, and so on. - Exit 0: success. Stdout may be JSON:
decision/reason,continue: falsewithstopReason(ends the turn),systemMessage, andhookSpecificOutput(permissionDecision,updatedInput,additionalContext). - Exit 2: blocks. Stderr is the reason, and it goes to the model.
- Other exit codes: errors that don't block. They're shown to you.
Environment and timeouts:
LARIK_PROJECT_DIRis set, and so isCLAUDE_PROJECT_DIRfor compatibility.- The default timeout is 60s.
- Matching hooks run in parallel.
Precedence: a hook deny beats everything. Permission deny rules beat a hook allow.
Prompt hooks ask a model instead of running a command, for checks that need judgment:
{ "hooks": { "Stop": [ { "hooks": [
{ "type": "prompt", "prompt": "Did the agent run the tests and see them pass? Look at the transcript at transcript_path in: $ARGUMENTS" }
] } ] } }
$ARGUMENTSbecomes the hook input as JSON; without it, the input is appended.- The model answers
{"ok": true}or{"ok": false, "reason": "…"}.ok: falseacts like exit code 2 for that event: aStophook makes the agent continue with the reason, aPreToolUsehook denies the call, aUserPromptSubmithook blocks the prompt. - It runs on the hook's
model(a provider/model or a routing role) if set, otherwise on yourexplorerole, otherwise on the session's model. Its cost counts toward the session and its budget. The default timeout is 30s. - An answer that can't be read, a timeout or an error doesn't block; you see a message.
Trust: hooks in the shared .larik/settings.json don't run until you run /hooks approve. The approval is pinned to the hook set's content. Hooks in personal settings always run.
Instructions are loaded from these files:
AGENTS.md(orCLAUDE.md) in each directory from the repo root down to the working directory.~/.config/larik/AGENTS.md.
They are read at the start of each fresh context, so after editing one, /clear picks up the change.