diff --git a/docs/cli/hooks.mdx b/docs/cli/hooks.mdx new file mode 100644 index 00000000000..11768d69030 --- /dev/null +++ b/docs/cli/hooks.mdx @@ -0,0 +1,223 @@ +--- +title: "CLI Hooks" +description: "Use Continue CLI hooks to intercept events, run shell or HTTP handlers, and enforce custom workflow rules." +--- + +Continue CLI hooks let you run custom handlers when key events happen during a `cn` session. You can use them to validate tool calls, add context, log activity, or block risky actions before they run. + +Hooks are compatible with Claude Code's settings format, so existing hook configurations can often be reused with Continue. + +## Where to configure hooks + +Continue loads hooks from these settings files, in precedence order: + +| Location | Scope | +|----------|-------| +| `~/.claude/settings.json` | User-global Claude-compatible config | +| `~/.continue/settings.json` | User-global Continue config | +| `.claude/settings.json` | Project config | +| `.continue/settings.json` | Project config | +| `.claude/settings.local.json` | Project-local config | +| `.continue/settings.local.json` | Project-local config | + +When multiple files define hooks, Continue merges them and runs all matching handlers. + +Use `.continue/settings.local.json` when you want personal hooks that should stay out of Git. + +## Basic structure + +Add a `hooks` object to one of the settings files above: + +```json +{ + "hooks": { + "PreToolUse": [ + { + "matcher": "^Bash$", + "hooks": [ + { + "type": "command", + "command": "python3 .continue/hooks/check_bash.py", + "statusMessage": "Checking Bash command" + } + ] + } + ], + "PostToolUse": [ + { + "matcher": "^Edit$", + "hooks": [ + { + "type": "http", + "url": "https://example.com/continue-hooks", + "headers": { + "Authorization": "Bearer ${CONTINUE_HOOK_TOKEN}" + }, + "allowedEnvVars": ["CONTINUE_HOOK_TOKEN"] + } + ] + } + ] + } +} +``` + +Each event contains one or more matcher groups: + +- `matcher` is a regex string used to decide whether the group should run +- `hooks` is the list of handlers to execute when the matcher matches + +An empty matcher, omitted matcher, or `"*"` matches everything. + +## Hook types + +| Type | What it does | Notes | +|------|--------------|-------| +| `command` | Runs a shell command and sends the event payload to stdin as JSON | Supports `async`, `timeout`, and `statusMessage` | +| `http` | Sends the event payload to an HTTP endpoint with a `POST` request | Supports custom headers and env var interpolation | +| `prompt` | Reserved for single-turn LLM hook handling | Not yet implemented | +| `agent` | Reserved for multi-turn agent hook handling | Not yet implemented | + +## Event types + +Continue accepts the Claude Code-compatible hook schema, but the CLI only +emits a smaller set of events today. + +These are the event names currently emitted by the CLI hooks system: + +| Event | Matcher runs against | +|-------|----------------------| +| `PreToolUse` | `tool_name` | +| `PostToolUse` | `tool_name` | +| `PostToolUseFailure` | `tool_name` | +| `PermissionRequest` | `tool_name` | +| `UserPromptSubmit` | No matcher support | +| `SessionStart` | `source` | +| `SessionEnd` | `reason` | +| `Stop` | No matcher support | +| `Notification` | `notification_type` | +| `PreCompact` | `trigger` | + +The hook schema also includes additional Claude-compatible event names such as +`PermissionRequest`, `SubagentStart`, `SubagentStop`, `ConfigChange`, +`TeammateIdle`, `TaskCompleted`, `WorktreeCreate`, and `WorktreeRemove`. +Those names are reserved for future support, but the Continue CLI does not emit +them yet, so configuring handlers for them will currently have no effect. + +Hook handlers receive a JSON payload on stdin or in the HTTP request body. Every event includes common context such as: + +- `session_id` +- `transcript_path` +- `cwd` +- `permission_mode` + +Tool-related events also include fields such as `tool_name`, `tool_input`, and `tool_use_id`. + +## Exit code and blocking behavior + +Command hooks use these exit code semantics: + +| Result | Behavior | +|--------|----------| +| `0` | Continue normally | +| `2` | Block the action. `stderr` becomes the feedback shown to the user | +| Any other non-zero code | Treated as a non-blocking hook failure | + +HTTP hooks behave a little differently: + +- A non-2xx response is treated as a non-blocking hook failure +- Returning JSON with `"decision": "block"` blocks the action + +Hooks can also return structured JSON to influence behavior more precisely: + +```json +{ + "hookSpecificOutput": { + "hookEventName": "PreToolUse", + "permissionDecision": "deny", + "permissionDecisionReason": "Destructive Bash commands require manual review" + } +} +``` + +For `PreToolUse`, hooks can: + +- allow, deny, or ask for permission +- attach `additionalContext` +- provide `updatedInput` + +## Example: block dangerous Bash commands + +This example checks Bash tool calls before they run: + +```json +{ + "hooks": { + "PreToolUse": [ + { + "matcher": "^Bash$", + "hooks": [ + { + "type": "command", + "command": "python3 .continue/hooks/check_bash.py" + } + ] + } + ] + } +} +``` + +Example handler: + +```python +import json +import sys + +payload = json.load(sys.stdin) +command = payload.get("tool_input", {}).get("command", "") + +if "rm -rf" in command: + print("Blocked dangerous command", file=sys.stderr) + raise SystemExit(2) +``` + +Because the script exits with code `2`, Continue blocks the tool call and shows the error message to the user. + +## Example: log tool activity to an HTTP endpoint + +```json +{ + "hooks": { + "PostToolUse": [ + { + "matcher": "^Edit$", + "hooks": [ + { + "type": "http", + "url": "https://example.com/hooks/post-tool-use", + "headers": { + "Authorization": "Bearer ${CONTINUE_HOOK_TOKEN}" + }, + "allowedEnvVars": ["CONTINUE_HOOK_TOKEN"] + } + ] + } + ] + } +} +``` + +This is useful for audit logging, notifications, or custom workflow automation after a tool succeeds. + +## Disabling hooks + +To turn off hooks temporarily, set `disableAllHooks` in your settings file: + +```json +{ + "disableAllHooks": true +} +``` + +Continue still loads the configured hooks, but it skips executing them while this flag is enabled. diff --git a/docs/docs.json b/docs/docs.json index 379837394d8..bee64e2fc0d 100644 --- a/docs/docs.json +++ b/docs/docs.json @@ -68,6 +68,7 @@ "cli/tui-mode", "cli/headless-mode", "cli/configuration", + "cli/hooks", "cli/tool-permissions" ] }