ReferenceHooks reference

Hooks reference

This page lists every hook event Syntic Code fires, what each one sends to your hook, and what your hook can send back. For a gentler introduction with worked examples, start with the hooks guide.

Configuration format

Hooks live under a hooks key in any settings file, grouped by event name. Each event holds a list of matcher groups; each group holds a list of hooks.

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          { "type": "command", "command": "./scripts/guard-shell.sh", "timeout": 15 }
        ]
      }
    ]
  }
}

Hooks are read from ~/.syntic/settings.json, ~/.syntic/hooks.json, the project’s .syntic/settings.json, .syntic/hooks.json and .syntic/settings.local.json, your organization’s managed policy, and every enabled plugin’s hooks/hooks.json. All of them are combined. Project files only count once you trust the folder. An entry Syntic Code cannot parse is skipped; the rest still load.

Hook fields

FieldApplies toMeaning
typeallcommand (the default when left out), http, prompt or agent.
commandcommandThe command line to run.
argscommandA list of arguments. When present, command is started directly as a program with these arguments, without a shell.
shellcommandbash (default; Git Bash on Windows) or powershell.
asynccommandtrue starts the hook in the background. The event does not wait, and the hook’s answer is ignored.
asyncRewakecommandImplies async. If the background hook exits with code 2, its report is handed to the agent at its next step, or wakes an idle session.
urlhttpWhere the event JSON is POSTed.
headershttpExtra request headers. Values may use $NAME or ${NAME} for variables listed in allowedEnvVars.
allowedEnvVarshttpEnvironment variables that may be expanded in headers. Any other name expands to an empty string.
promptprompt, agentThe instruction. $ARGUMENTS is replaced with the event JSON.
modelprompt, agentModel used to judge the event.
timeoutallSeconds before the hook is stopped. Default 60.
statusMessageallText shown on the working line while the hook runs.
oncealltrue runs the hook only the first time it matches in a session.
iftool eventsA permission-rule pattern such as Bash(git push*). The hook only runs when the tool call matches it.

Matchers

The matcher on a group is tested against one field of the event (listed per event below).

  • Missing, empty or * matches everything.
  • Letters, digits, _ and | only: an exact match against one of the |-separated names, for example Edit|Write.
  • Anything else is treated as a regular expression that must match the whole value, for example mcp__github__.*. An invalid expression matches nothing.
  • Events without a matcher field run every group, whatever its matcher says.

Common input fields

Each hook gets one JSON object. Command hooks read it on standard input, http hooks receive it as the request body, and prompt/agent hooks see it in place of $ARGUMENTS.

FieldPresentValue
hook_event_namealwaysThe event name, such as PreToolUse.
session_idalwaysThe main session’s id, also inside a subagent.
cwdmost eventsThe folder the next shell command would run in.
permission_modemost eventsdefault, acceptEdits, auto, plan, bypassPermissions or dontAsk.
transcript_pathwhen sessions are savedPath of the session’s transcript.jsonl.
agent_id, agent_typeinside a subagentWhich subagent fired the event. agent_type also appears when the main session runs as a custom agent (--agent).
effortwhen an effort level is set{ "level": "<level>" }.

Events

Tool events

EventFiresMatcher fieldExtra input
PreToolUseBefore a tool runstool_nametool_name, tool_input, tool_use_id
PostToolUseAfter a tool succeedstool_nameAbove, plus duration_ms and tool_response (the first 50,000 characters; tool_response_truncated: true when cut)
PostToolUseFailureAfter a tool returns an errortool_nametool_name, tool_input, tool_error, duration_ms
PermissionRequestJust before an approval dialog would showtool_nametool_name, tool_input, reason
PermissionDeniedAfter a call is refused, by a rule, a hook or youtool_nametool_name, tool_input, reason

Tool names are the ones on the Tools page; MCP tools are named mcp__<server>__<tool>.

Prompt and turn events

EventFiresMatcher fieldExtra input
UserPromptSubmitYou send a messagenoneprompt
StopThe agent is about to end its turnnonestop_hook_active, last_assistant_message, background_tasks, session_crons
StopFailureA turn ends in an errorthe error texterror
MessageDisplayA finished piece of reply text is about to be shownnonemessage
NotificationSyntic Code wants your attentionnotification_typemessage, and notification_type (permission_prompt when a tool needs approval; absent for the “finished its turn” notice)

stop_hook_active is true when a Stop hook already sent the agent back this turn. Use it to avoid sending it back forever. background_tasks lists running background commands and agents; session_crons lists this project’s scheduled prompts.

Session events

EventFiresMatcher fieldExtra input
SetupWith --init, --init-only or --maintenance, before SessionStarttriggertrigger: init or maintenance
SessionStartOnce, before the first turnsourcesource: startup (interactive) or headless (-p)
SessionEndWhen the session shuts downreasonreason: exit (interactive) or headless-complete
InstructionsLoadedA memory or rules file is loadedload_reasonfile_path, memory_type (Managed, User, Local, Project), load_reason (session_start, nested_traversal, path_glob_match, include, compact), and when relevant trigger_file_path, parent_file_path, globs
ConfigChangeA settings file changes on disksourcesource (user_settings, project_settings, local_settings, policy_settings), file_path
CwdChangedThe shell’s working folder changesnonecwd, previous_cwd
FileChangedA watched file is added, changed or removedfile name (the last part of the path)file_path, event (add, change, unlink)
DirectoryAddedA working folder is added (/add-dir)nonedirectory
PreModelSwitchThe model is about to changemodelprevious_model, model, source (session or host)
PostModelSwitchThe model has changedmodelSame as above

A FileChanged group’s matcher also tells Syntic Code which file names to watch, for example "matcher": ".envrc|.env". Hooks can add more files with watchPaths.

Compaction events

EventFiresMatcher fieldExtra input
PreCompactBefore automatic compactiontriggertrigger: auto, or prompt-too-long when a request overflowed the window
PostCompactAfter itnonetrigger, message_count

Subagent, task and team events

EventFiresMatcher fieldExtra input
SubagentStartA subagent beginsagent_typesubagent_type, model
SubagentStopA subagent is about to finishnonesubagent_type, stop_hook_active, last_assistant_message, background_tasks, session_crons
TaskCreatedThe agent creates a tasknonetask_id, task_subject, task_description
TaskCompletedA task is marked donenonetask_id, task_subject
TeammateIdleA teammate in an agent team runs out of worknoneteammate_name, team_name
WorktreeCreateA git worktree is about to be madenonename, base_dir
WorktreeRemoveA worktree is removednoneworktree_path

MCP events

EventFiresMatcher fieldExtra input
ElicitationAn MCP server asks you a questionmcp_server_namemcp_server_name, plus the server’s request (message, requestedSchema)
ElicitationResultThe question has an answermcp_server_namemcp_server_name, action (accept, decline, cancel), content

Environment variables

Command hooks inherit your environment, minus Syntic Code’s own sign-in credentials, plus:

VariableValue
SYNTIC_PROJECT_DIRThe project folder.
SYNTIC_SESSION_IDThe session id.
SYNTIC_EFFORTThe effort level, when one is set.
SYNTIC_PLUGIN_ROOT, SYNTIC_PLUGIN_DATAFor plugin hooks: the plugin’s folder and its data folder. ${SYNTIC_PLUGIN_ROOT} and ${SYNTIC_PLUGIN_DATA} in command are replaced too.
SYNTIC_USER_CONFIG_<KEY>For plugin hooks: the plugin’s options.

Output

Exit codes (command hooks)

CodeMeaning
0Success. Standard output is read as a JSON answer if it is one. For UserPromptSubmit, SessionStart and Setup, plain text output is added as context for the agent.
2Block. Standard error becomes the reason, and the agent sees it. What “block” means depends on the event (see below).
anything elseA non-blocking error. It is reported and the session carries on.

A hook that cannot start, runs past its timeout, or (for http) cannot be reached contributes nothing and never blocks. When a hook times out, its whole process group is stopped.

An http hook’s answer is the response body, in the same JSON shape a command prints. A non-2xx status is reported as a non-blocking error. A prompt or agent hook answers {"ok": true} or {"ok": false, "reason": "…"}; false blocks.

JSON fields

Print one JSON object on standard output for finer control.

FieldEffect
continuefalse stops the whole run after this hook.
stopReasonShown to you when continue is false.
decisionblock (or deny) blocks. approve (or allow) approves.
reasonWhy. Shown to the agent on a block.
systemMessageA note for you, not the agent.
suppressOutputtrue hides the systemMessage notice.
asynctrue declares the hook detached; its answer is ignored.
reloadSkillstrue rescans skill folders (SessionStart).
terminalSequenceA bell, window title or desktop notification for Syntic Code to write to its terminal. Only BEL, OSC 0/1/2 titles and OSC 9/777/99 notifications are accepted; anything else is ignored.
hookSpecificOutputEvent-specific fields, below. Its hookEventName must match the event or it is ignored.

hookSpecificOutput fields

FieldEventsEffect
permissionDecisionPreToolUse, PreModelSwitchallow, deny, ask or defer. For a model switch, ask asks you to confirm.
permissionDecisionReasonPreToolUseThe reason shown with the decision.
updatedInputPreToolUseReplacement tool input. A later hook’s rewrite wins.
additionalContextmost eventsText handed to the agent with the next step. On Stop and SubagentStop, it sends the agent back with your feedback.
updatedMCPToolOutputPostToolUseReplacement output for an MCP tool.
decisionPermissionRequest{ "behavior": "allow", "updatedInput": … } or { "behavior": "deny", "message": "…", "interrupt": true }. Answers the dialog without showing it.
watchPathsSessionStart, CwdChanged, FileChangedFiles to watch for FileChanged.
sessionTitleSessionStart, UserPromptSubmitNames the session.
worktreePathWorktreeCreateWhere the hook created the worktree. The last line of plain output also works.
displayText, hideMessageMessageDisplayShow different text, or nothing. The agent still sees the original.
action, contentElicitation, ElicitationResultAnswer the server for you: accept with content, decline or cancel.

What a block does

EventEffect
PreToolUseThe tool does not run. The reason becomes the tool result.
PostToolUseThe tool already ran; the reason is added to its result for the agent.
UserPromptSubmitYour message is not sent.
Stop, SubagentStopThe agent keeps working, with the reason as its next instruction. At most 8 times in a row (SYNTIC_STOP_HOOK_BLOCK_CAP).
TeammateIdleThe teammate is sent back to work with the reason, at most 3 times in a row. continue: false stops it.
TaskCreatedThe task is not created.
ConfigChangeThe settings change is not applied.
PreModelSwitchThe model stays as it was.
WorktreeCreateThe worktree is not created.
ElicitationThe question is declined.

StopFailure, SessionEnd, PreCompact, PostCompact, InstructionsLoaded, PermissionDenied and Notification only observe; their answers change nothing.

Hooks and permission rules

From strongest to weakest:

  1. A deny rule in settings always refuses the call.
  2. An ask rule always shows the dialog, even when a hook says allow.
  3. A PreToolUse hook’s deny refuses the call.
  4. A PreToolUse hook’s ask forces the dialog.
  5. A PreToolUse hook’s allow skips the dialog the mode would have shown.
  6. Otherwise the normal flow decides. Before a dialog appears, PermissionRequest hooks may answer it.

Limits

LimitValue
Default timeout60 seconds for every hook type
tool_response sent to PostToolUse50,000 characters
Hook text passed to the agent10,000 characters; longer text is saved to a file and the agent gets the first 2,000 characters and the path
Stop hook continuations in a row8 (change with SYNTIC_STOP_HOOK_BLOCK_CAP)

Turning hooks off

SwitchEffect
"disableAllHooks": true in your settingsOnly organization hooks run.
"disableAllHooks": true in managed policyNo hooks run.
"allowManagedHooksOnly": true in managed policyOnly policy hooks run.
"allowedHttpHookUrls"http hooks run only when their URL matches one of these patterns.
--bareNo hooks from settings files.
SYNTIC_DISABLE_HOOKS=1No hooks at all.
⚠️

Command hooks run with your user rights. Read a repository’s hooks before you trust its folder.

Debugging

  • /hooks lists every configured hook with its number and source file.
  • syntic --debug hooks logs each hook run and its output.
  • In -p runs, --output-format stream-json --verbose --include-hook-events reports each hook as it starts and finishes.
  • Test a script by hand: echo '{"hook_event_name":"PreToolUse","tool_name":"Bash","tool_input":{"command":"ls"}}' | ./my-hook.sh; echo $?.