-
Notifications
You must be signed in to change notification settings - Fork 4.3k
docs: add a CLI hooks guide #11353
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
docs: add a CLI hooks guide #11353
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 | | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. change "the user" to "you" |
||
| | 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. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Change "the user" to you or just remove. |
||
|
|
||
| ## 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. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Could you remove this last sentence? Loads but doesn't execute doesn't mean much I think |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -68,6 +68,7 @@ | |
| "cli/tui-mode", | ||
| "cli/headless-mode", | ||
| "cli/configuration", | ||
| "cli/hooks", | ||
| "cli/tool-permissions" | ||
| ] | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why include "Not yet implemented" here?