Skip to content
LoopCodeDocs

Extend LoopCode

Hooks

Run your own commands at fixed points in LoopCode's agent loop — format after edits, block dangerous commands, filter test output, add context, or keep the agent working. Compatible with Claude Code hooks.

Why hooks

A prompt asks the model to do something; a hook makes it happen. Use hooks for anything that must not be skipped: formatting after every edit, refusing a dangerous command, filtering a huge test log down to the failures before it reaches the context window, or writing an audit record.

Hooks are ordinary programs — written in whatever your team already uses — configured in settings files. The events, payloads and decisions have the same names Claude Code uses, so hooks written for it mostly work here unchanged.

Events

EventWhenCan
PreToolUseBefore a tool call runsAllow, deny or ask; rewrite the tool's input
PostToolUseAfter a tool callAdd context for the model
UserPromptSubmitWhen you send a messageRewrite the prompt; add context; block it
SessionStartWhen a session startsAdd context
StopWhen the agent is about to finish its turnKeep it working, with an instruction
SubagentStopWhen a subagent finishesKeep it working
PreCompactBefore the conversation is compactedAdd instructions for the summary

Configure

.loopcode/settings.json
{
  "hooks": {
    "PostToolUse": [
      { "matcher": "Edit|MultiEdit|Write", "hooks": [{ "type": "command", "command": "npx prettier --write \"$(jq -r .tool_input.path)\"" }] }
    ],
    "PreToolUse": [
      { "matcher": "Bash", "hooks": [{ "type": "command", "command": "./scripts/hooks/no-prod-db.sh", "timeout": 10 }] }
    ],
    "Stop": [
      { "hooks": [{ "type": "command", "command": "./scripts/hooks/require-changelog.sh" }] }
    ]
  }
}
FieldMeaning
matcherFor tool events: a tool name, a | list (Edit|Write), or a regular expression. Omit it, or use *, for every call
hooks[].commandThe command to run, through your shell, in the project folder
hooks[].timeoutSeconds before it is stopped (default 60, at most 600)
hooks[].asynctrue runs it without waiting; its result is ignored. For notifications and logging

Hooks from all settings files accumulate. /hooks lists the hooks in force and which files they came from.

What a hook receives

The event as JSON on standard input:

json
{
  "hook_event_name": "PreToolUse",
  "session_id": "…",
  "cwd": "/Users/you/code/shop",
  "permission_mode": "auto",
  "tool_name": "Bash",
  "tool_input": { "command": "psql $PROD_DATABASE_URL -c 'drop table orders'" }
}
FieldPresent for
tool_name, tool_inputTool events
tool_result (text, ok)PostToolUse
promptUserPromptSubmit
session_start_sourceSessionStart
last_assistant_message, stop_hook_activeStop and SubagentStop. Check stop_hook_active to avoid sending the agent back in a loop
agent_typeSubagentStop
trigger (auto or manual), custom_instructionsPreCompact

The environment also has LOOPCODE_PROJECT_DIR (the project folder) and LOOPCODE_HOOK_EVENT.

What a hook can decide

Exit codes. 0 is success. 2 blocks the action, with whatever the hook wrote to standard error as the reason, which the agent sees. Any other exit code is treated as a broken hook — reported, but not a veto.

JSON on standard output, at the top level or under hookSpecificOutput:

FieldEffect
permissionDecisionPreToolUse: allow, deny or ask, overriding the permission check for this call
permissionDecisionReasonWhy — shown to the agent
updatedInputPreToolUse: replace fields of the tool's input before it runs
updatedPromptUserPromptSubmit: replace the prompt before the model sees it
additionalContextAny event: text handed to the model as extra context
systemMessageA message for you, shown in the transcript
decision: "block" with reasonStop: keep the agent working, with reason as its instruction. PreToolUse: deny
scripts/hooks/no-prod-db.sh
#!/bin/sh
command=$(jq -r '.tool_input.command // ""')
case "$command" in
  *PROD_DATABASE_URL*) echo "Never touch the production database from an agent." >&2; exit 2 ;;
esac
exit 0

Safety

Hooks run with your permissions, without asking. A repository's committed .loopcode/settings.json can define hooks, so read the settings of a repository you do not trust before working in it. "disableAllHooks": true turns every hook off — put it in that project's .loopcode/settings.local.json, which is read last.