Skip to content

Use hooks and slash commands

Slash commands turn /… input into session controls or extension macros. Hooks let enabled plugins and mini apps, or an invoked skill or agent, run handlers at defined runtime boundaries.

What you can do

OutcomeMechanismBoundary
Run session controlsSelect a core command for models, reasoning, context, loops, or extensions.Client-scoped commands depend on the active client.
Invoke an extension macroRun a command from an enabled plugin or mini app, or a user-invocable skill.Macros start model work; during a run they wait in the queue.
Gate prompts or toolsUse native onPreToolCall or declarative UserPromptSubmit and PreToolUse hooks.The extension or run-scoped declaration must be active.
Transform model-facing dataRewrite prompts, tool inputs, tool outputs, assistant text, or added context.Each hook contract controls which persisted data can change.
Observe lifecycle eventsReceive run, worker, context-checkpoint, and human-input events.Observer hooks cannot answer a human-in-the-loop request.

How it works

Slash-command dispatch

Web/desktop derives its built-in menus from the shared slash catalog, then merges enabled plugin and mini app commands and user-invocable skills for the active scope.

OrderOwnerDispatch behavior
1Compatible clientHandles client commands such as /fork and /open before an API turn.
2Core API catalogResolves canonical names and aliases to typed control handlers.
3Enabled extensionResolves a plugin's /<plugin>:<command>, a workflow, a mini app or mod command.
4User-invocable skillResolves /<skill-name> when no earlier owner claimed it.
5Unresolved gateReturns an error and suggestions instead of sending command-shaped input to the model.

A core name or alias wins a collision. A plugin's command (commands/*.md) and a folder's (.claude/commands/*.md) are skills, as in Claude Code: they run through the same plugin kit session as a skill, with its arguments, variables, ! commands and @ files (see Skill authoring). A plugin's command wins a skill of its plugin with the same name. There is no bare alias: a command named after its plugin is /<plugin>:<plugin>. A workflow (.claude/workflows, ~/.claude/workflows or a plugin's) typed as /<name> asks the agent to run it with the Workflow tool; a skill of the same name runs instead.

Mini app commands are agent prompt templates: the backend validates positional arguments and static choices, expands their variables, then starts a macro run. Dynamic option scripts stay server-side; clients receive only their returned choices.

A mini app command's arguments are positional. Non-final text and choice fields consume whitespace-delimited tokens; only a final text field captures the remainder. Static choices must match a declared value, while dynamic choices accept any non-empty token without checking the returned menu. A conditional argument whose earlier value does not match is skipped. Extra tokens are not rejected by the declared-argument parser; $ARGUMENTS can still include the complete raw input.

An argument containing ! command syntax or @path does not start another preprocessing pass. A skill's or command's ! commands meet the agent's permission rules before they run; an argument reaches them as data, never as shell code.

CategoryCore commands
Autonomous/loop
Configuration/agents, /model (/models), /effort, /output-style, /theme, /plan, /permissions, /config, /add-dir
Context/clear, /resume, /rename, /rewind, /copy, /fork, /open (client-scoped), /compact, /export
Information/help, /usage (/cost), /context, /recap, /status, /diff
Memory and skills/memory, /skills (/skill), /init
Extensions/mcp, /plugin, /mini-app, /hooks

A command that takes an argument offers it as crumbs: pick from the list, or type the value.

/plugin manages Claude Code plugins and their marketplaces; /mini-app lists, creates, switches on and off, removes and reloads mini apps (see Mini apps).

Everyday Claude Code commands

Claude Code commands in Localix lists the built-in Claude Code commands Localix runs, what each does here, and what the others answer.

Native mini app hooks

Native hooks are callbacks returned by an enabled mini app's register(ctx) function.

HookPrimary effectBoundary
onSessionStartObserve the first run of a session through the app's context.Fires once per session per API process and is not awaited.
onUserMessageRewrite the model-facing prompt.The persisted user message does not change.
onPreToolCallObserve, rewrite, or block built-in, extension, and MCP calls.A rewrite is schema-checked when the tool exposes a compatible schema.
onPostToolCallReplace result content or append a note.Appended text is capped at 1,024 UTF-16 code units per hook result.
onAssistantMessageReplace persisted assistant text or add message bindings.Content must be non-empty; bindings are validated and capped.
onSessionEndObserve the end of each run.It is not a logical session-deletion event.
onSubagentStopObserve a child session reaching a terminal status.Observer-only.
onHitlRequest / onHitlResolvedObserve human-input request lifecycle.These hooks cannot answer or veto the request.
onHumanAssignedObserve a board card assigned to the person.Receives sessionId, messageId, from and a text excerpt; observer-only.

Native rewrites compose in mini app load order. Tool pre-hooks stop at the first block; post-hooks all run and later hooks see earlier replacements.

Post-hooks also see execution errors converted into tool results; a pre-hook block skips execution and post-hooks. Assistant hooks can add at most eight bindings in total, with an 8 MiB payload cap per binding. These caps do not impose an aggregate limit on replacement message or tool text.

Declarative hooks

Declarative hooks use the supported hooks JSON schema from enabled plugins or hooks frontmatter on the invoked skill or delegated agent. Localix dispatches these events:

Session events are SessionStart, SessionEnd, InstructionsLoaded, ConfigChange, CwdChanged, FileChanged, PreCompact, PostCompact, PreModelSwitch, PostModelSwitch and Notification. Turn events are UserPromptSubmit, UserPromptExpansion, Stop, StopFailure and MessageDisplay. Tool events are PreToolUse, PermissionRequest, PermissionDenied, PostToolUse, PostToolUseFailure and PostToolBatch. Agent events are SubagentStart, SubagentStop, TaskCreated and TaskCompleted. Elicitation, ElicitationResult and WorktreeCreate complete the twenty-nine.

Setup, DirectoryAdded and WorktreeRemove are accepted but never fire, as Localix has no matching moment; a hook on any other event is reported in validation and never fires.

PostToolUseFailure runs in place of PostToolUse when a tool throws; its handlers receive the error, and their context or decision: "block" reason is added to the failed result.

PreCompact fires with trigger auto before a context checkpoint is committed, and with trigger manual and the typed text as custom_instructions when /compact asked for it; PostCompact follows with the same trigger. A block skips a proactive checkpoint, and the turn continues with the uncompacted view. A block on a checkpoint that recovers from a context-limit error fails the turn. A checkpoint taken only because the history does not fit the request is announced to PreCompact after it is taken, and that call cannot block. After a checkpoint, the next run fires SessionStart with source compact.

SessionStart fires on a session's first run in the API process. Its source is startup for a new session, fork for a forked one, resume for a session that ran before the API last started, and clear or compact after /clear or a checkpoint. In a run nobody is typing into, such as a scheduled one, its initialUserMessage is the user turn before the run's prompt; with reloadSkills, skills the hook installed are named to the agent and listed from the next run on.

SessionEnd fires when a session ends, not a run: /clear fires it with reason clear, deleting a session with other, and quitting the app with prompt_input_exit for each session that started and is still open. At quit all the hooks share Claude Code's 1.5 second budget; one that runs longer is left behind so the app can close.

A handler's statusMessage is the chat's activity line while it runs. A non-blocking error (another exit code, a timeout, a hook that cannot start, output that is not valid JSON) shows a <event> hook error notice in the transcript, as does the exit-2 stderr of SessionStart, SubagentStart and the other events that only observe. Claude Code's allowedHttpHookUrls and httpHookAllowedEnvVars settings hold every http hook: one whose URL matches no pattern does not run, and its headers read only the variables both lists name.

Matching handlers run in parallel, as in Claude Code, at most twelve prompt or agent handlers at once. Each sees the same input, and their answers fold in declaration and plugin load order: a deny from any PreToolUse handler stops the call without stopping the others from running, and the last updatedInput or PostToolUse replacement declared is the one applied.

EventEffect of a blocking verdict
UserPromptSubmitRejects the prompt on any block; the message ends with the prompt, unless suppressed (below).
PreToolUseStops the tool on any deny; ask and allow shape the permission prompt (below).
PostToolUseCannot undo execution; a block reason becomes added result context.
PostToolUseFailureCannot block; added context is appended to the failed result.
StopRequests another run with the reason; eight in a row end the turn, and a tool call resets that.
Stop additionalContextRequests the same bounded run as feedback, without a block.
Scoped child SubagentStopCan request the same bounded continuation in the child; parent observation does not restart it.
PreCompactSkips a proactive checkpoint; fails a turn whose checkpoint recovers from a context-limit error.
Other lifecycle eventsDo not stop the run; context or observer effects depend on the event.

A command or http handler with "onFailure": "block" turns its own failure into a block: when it cannot start, exits with a code other than 0 or 2, times out, or answers with invalid output, the event acts as on exit 2, and a PermissionRequest is denied. The error names the hook and says failed; blocking because onFailure is "block". Stop, SubagentStop, TaskCompleted and background (async) handlers ignore the field.

Tool calls reach hooks in Claude Code's names and shapes: Hook tool calls.

A PreToolUse ask asks even where your rules allow the call, with its reason on the card. An allow skips only a mode's questions about how a call is made; deny rules, named ask rules and irreversible calls stand. defer needs a headless caller: it is warned about and ignored.

Matcher, if, and once

A tool-event matcher meets the call's Claude Code tool name (see Hook tool calls), mcp__<server>__<tool> for an MCP tool, and no other spelling. Empty, omitted, or * matchers match all. Simple names separated by | or commas compare exactly, case included (Bash, not bash, is Claude Code's name); other patterns are unanchored JavaScript regular expressions. Invalid regexes do not match. UserPromptSubmit and Stop ignore group matchers; other lifecycle matchers inspect the event's source, reason, agent type, notification type, or checkpoint trigger.

A matcher meets the Claude Code name of the call itself: a file listing is Glob and a content search Grep, so a Glob matcher does not fire for a content search. MultiEdit matches an edit with several replacements and NotebookEdit an edit of an .ipynb file.

A handler's if is one permission-style rule evaluated against the original tool input before rewrites, read in Claude Code's shape. A Bash rule matches any command of a batch. A file rule anchors as in Claude Code: src/** is the working directory's src, // the filesystem root and ~/ your home folder. Agent(Explore), Skill(deploy), WebFetch(domain:example.com) and Tool(param:value) read the agent type, the skill, the host and the named field. It applies only to PreToolUse, PostToolUse, PostToolUseFailure, PermissionRequest and PermissionDenied; carrying it on another event prevents that handler from running. Unparseable rules or unavailable comparison fields let the handler run. This filter does not grant tool permission.

Configuration

Contribution locations

ContributionAccepted source
Plugin slash commandThe default commands/ tree, or commands paths or inline entries in .claude-plugin/plugin.json
Declarative plugin hooksDefault hooks/hooks.json, then hooks paths or inline objects in .claude-plugin/plugin.json
Run-scoped hookshooks frontmatter on the invoked skill or delegated agent
Mini app slash commandAn entry under commands in mini-app.json, which carries its own prompt
Native mini app hookshooks returned from the mini app's register(ctx) function

Inline commands win same-name collisions with command Markdown files. Explicit file/path entries win over the default directory, and nested paths become colon-separated names. Paths listed in .claude-plugin/plugin.json replace the default tree, as in Claude Code; a root plugin.json is not read. Markdown commands need a non-empty body in a regular UTF-8 file up to 1 MiB. Discovery considers at most 64 declared roots, eight directory levels, and 2,000 entries per scanned root, and checks canonical paths against the plugin directory.

Plugin command example and options

A command is a Markdown file in the plugin, commands/review.md:

Markdown
---
description: Review a change and report concrete findings.
arguments: [request]
---

Review the requested change and report concrete findings: $request

For a plugin named team-tools, it runs as /team-tools:review <request>, here and in Claude Code.

A mini app declares its commands in mini-app.json, each with its prompt and the chips and pickers of its args; ${name} takes each argument's value. In a mini app named team-board this runs as /team-board:review <request>:

JSON
{
  "commands": {
    "review": {
      "description": "Review a change and report concrete findings.",
      "prompt": "Review the requested change and report concrete findings: ${request}",
      "args": [{ "name": "request", "type": "text", "required": true }]
    }
  }
}

An options script runs directly through Bun when the composer requests choices for an enabled mini app. It receives { query, cwd, sessionId, argName, command, config } JSON on stdin and returns a JSON array of strings or { value, label?, description? } objects on stdout. Valid rows are normalized, with at most 200 returned. The run has an eight-second deadline and a 256 KiB stdout capture ceiling. A parse failure or timeout yields an empty menu; a non-zero exit alone does not discard otherwise usable stdout.

The working directory is the resolved session directory, otherwise the project directory, otherwise the shared Localix workspace. The script path must be lexically within the mini app's directory, but this path check does not resolve symlink targets. This subprocess has normal local access and runs outside Bash tool approval. Treat it as executable mini app code, rather than a sandboxed lookup.

Declarative handler support

HandlerPrimary mechanismBoundary
commandRuns the agent's Bash (bash -c; Git Bash on Windows), sends event JSON on stdin, and folds exit code plus output.Supported on every dispatched event.
httpPOSTs event JSON and folds a successful response like command output.Unsupported on SessionStart; network and non-2xx failures do not block.
mcp_toolCalls a gathered MCP tool with the handler's input as its arguments.Missing, disconnected, or failed tools do not block; skipped on SessionStart at launch and resume.
promptUses one auxiliary model call to return an allow/block verdict.The events listed below; requires a live provider connection.
agentRuns a bounded read-only verifier with Read and Search.The same events except PermissionRequest; needs the agent service and a live provider connection.

For mcp_tool arguments and prompt and agent verdicts, see Hook handlers.

HandlerDefault timeoutAdditional cap
command, http, mcp_tool600 secondsDeclared timeouts are capped at 1,800 seconds.
prompt30 secondsDeclared timeouts are capped at 1,800 seconds.
agent60 secondsDefault 50 turns; declared timeouts are capped at 1,800 seconds.

A prompt or agent hook's $ARGUMENTS is replaced by the event JSON; without it, the JSON is appended to the prompt. A backslash keeps a dollar sign literal (\$1.00 reads $1.00).

A command handler's ${CLAUDE_PROJECT_DIR}, ${CLAUDE_PLUGIN_ROOT} and ${CLAUDE_PLUGIN_DATA} are expanded by the shell from the child environment, not pasted into the command text, and a non-secret ${user_config.KEY} becomes ${CLAUDE_PLUGIN_OPTION_<KEY>} with its value in the environment. A project path or config value is therefore data even when it contains $(…), quotes or backticks. Quote the placeholder ("${CLAUDE_PROJECT_DIR}") when the value may contain spaces. Plugin monitor commands follow the same rule. An http handler's URL is not shell text, so its placeholders are substituted directly.

Stdout counts as JSON output only when it is one JSON object with nothing around it. Text that does not start with { or end with } is plain text; anything else that fails to parse is a non-blocking error, applies nothing and never becomes context. On exit 2 a JSON object is still read, and a deny or block reason in it replaces stderr as the message.

On Stop, SubagentStop, TaskCompleted and a plugin's UserPromptSubmit, exit 2 with empty stdout and a stderr that says No such file or directory is a non-blocking error, so a hook whose script is missing does not trap the agent. An http handler's 2xx body must be empty or a JSON object; plain text is an error.

Command-hook stdout and stderr capture is capped at 64 KiB each. A hook's additionalContext, systemMessage or plain stdout longer than 10,000 characters is saved to a file in the session's folder and replaced by that file's path and its first 2,000 characters, as in Claude Code.

A command handler with async: true cannot affect the current event. When it exits 0, the additionalContext and systemMessage of its JSON response reach the model with the session's next turn; print the JSON alone on stdout or on a line of its own. asyncRewake can wake an idle session when that command exits with code 2. That wake is armed when the hook starts and delivered at most once; a hook that exits any other way, or is lost to an API restart, wakes nobody.

What a blocked prompt shows

A blocked prompt never reaches the model, and the run fails with the hook's reason followed by Original prompt: and the text you sent. A hook that prints "suppressOriginalPrompt": true inside hookSpecificOutput leaves the text out, whether it blocks with decision: "block" or by exiting 2 (the JSON on stdout is still read). The flag changes only that message: the prompt stays in your conversation's transcript. A UserPromptExpansion hook may send it too, as Claude Code's types allow, but an expansion block never echoed the prompt, so there is nothing to leave out.

Limits

  • strictPluginOnlyCustomization with hooks in managed settings stops the hooks of user, project and local settings and of skills from your folders; the organization's hooks and plugins' keep running. See Use your Claude Code settings.
  • Declarative hooks enter through an enabled plugin, the invoked skill or agent's run-scoped frontmatter, and the hooks of your Claude Code settings files for the conversation's folder: ~/.claude/settings.json and managed settings always, a project's .claude/settings.json and .claude/settings.local.json once you trusted the folder in Claude Code. disableAllHooks and allowManagedHooksOnly limit them, plugins' included, as in Claude Code.
  • Enabling a hook-bearing plugin or mini app is a code-execution trust decision. Native and command hooks can run local code; HTTP hooks make network requests; MCP, prompt, and agent hooks invoke their named runtimes.
  • Unsupported event and handler combinations are skipped during parsing. They do not execute.
  • A declarative PreToolUse allow cannot bypass the Localix permission gate: it lifts only the mode-level questions about how a call is made, and a deny rule, a named ask rule or an irreversible call still stands. An ask always asks, and a deny always stops the tool.
  • Prompt, agent, MCP, HTTP, and command handler failures are non-blocking unless the handler returns a supported blocking verdict. Missing provider or MCP connections therefore fail open.
  • A malformed auxiliary-model verdict also allows the event to continue, and says so in a hook error line. Prompt hooks make billed model calls; agent hooks create verifier jobs, with a default of 50 turns rather than a fixed maximum for every declaration.
  • MCP hook calls still obey agent capability and permission rules. Calls requiring denial or confirmation do not execute through the hook path; their hook verdict fails open.
  • Command hooks spawn directly through the resolved shell, outside Bash tool approval. Timeout or cancellation kills the shell child; it does not undo prior effects or guarantee descendant cleanup.
  • Async command handlers are observer-only for the current event. Their decision fields cannot block or rewrite in-flight work.
  • Native callbacks other than onSessionStart isolate failures and use a five-second wait per callback. The timeout stops waiting without cancelling callback work. It cannot interrupt synchronous JavaScript that blocks the API event loop.
  • onSessionStart is fire-and-forget with rejected promises logged. A synchronous throw from the callback occurs before that promise handler and can propagate into run startup.
  • /fork, /open and the client commands above require web/desktop. A client without those surfaces omits them from its forwarded server command list.
  • A plugin, mini app or skill macro sent while a run is active waits in the queue as a whole command — its badge, model override, tool grants and hooks — and runs as its own turn.
  • /model walks the model picker's steps in the composer — provider, model, then optional reasoning, turns and context limit settings — and never prints a model list. See Switch models with /model. Typed as /model <name>, it switches the conversation's model by name.

Source of truth