Hooks reference changedhooks
Nearest release: v2.1.295, published an hour before upstream edited the page. Shown because the two are within 24 hours of each other. Nothing here says the release caused the edit.
Upstream edited this page at 8 Oct 2026 20:22 UTC, give or take a minute or two: the time comes from Anthropic’s own sitemap rather than from a commit. This site recorded the change at 8 Oct 2026 20:37 UTC.
Upstream edited
Recorded here
Lines+185added
Lines−25removed
From line
878
where the diff opens
First seen
14 Aug 2026
this site's first read of the page
Recorded edits92to this page, all time
#### Other ways to decline an elicitation #### Answer a form request from a script
The whole hunk
from line 878, old and new numbered
/
from line 878
878878| `PostCompact` | No | Shows stderr to user only |
879879| `PreModelSwitch` | Yes | Blocks the model switch and shows stderr to the user |
880880| `PostModelSwitch` | No | Shows stderr to user only; the model already switched |
881| `Elicitation` | Yes | Denies the elicitation |
881| `Elicitation` | Yes | Declines the request, and no dialog appears |
882882| `ElicitationResult` | Yes | Blocks the response (action becomes decline) |
883883| `WorktreeCreate` | Yes | Any non-zero exit code causes worktree creation to fail |
884884| `WorktreeRemove` | Yes | Any non-zero exit code causes worktree removal to fail if the directory still exists afterward. See [WorktreeRemove](#worktreeremove) for what happens to the directory |
from line 1025
10251025| PermissionDenied | `hookSpecificOutput` | `retry: true` tells the model it may retry the denied tool call; Claude Code ignores it for [no-verdict denials](#permissiondenied-decision-control) |
10261026| WorktreeCreate | path return | Command hook prints path on stdout; HTTP hook returns `hookSpecificOutput.worktreePath`. Hook failure or missing path fails creation |
10271027| WorktreeRemove | Exit code | Any non-zero exit code makes the removal fail if the directory still exists afterward. JSON output is discarded |
1028| Elicitation | `hookSpecificOutput` | `action` (accept/decline/cancel), `content` (form field values for accept) |
1029| ElicitationResult | `hookSpecificOutput` | `action` (accept/decline/cancel), `content` (form field values override) |
1028| Elicitation, ElicitationResult | `hookSpecificOutput` or top-level `decision` | `action` (accept/decline/cancel), `content` (form field values). `decision: "block"` also [declines](#other-ways-to-decline-an-elicitation) |
10301029| MessageDisplay | `hookSpecificOutput` | `displayContent` replaces the displayed text on screen. Display-only: the transcript and what Claude sees keep the original |
10311030| SessionStart, SubagentStart, PostModelSwitch | Context only | `hookSpecificOutput.additionalContext` adds context for Claude. SessionStart also accepts [`initialUserMessage`, `watchPaths`, `sessionTitle`, and `reloadSkills`](#sessionstart-decision-control). No blocking or decision control |
10321031| Setup, Notification, SessionEnd, PostCompact, InstructionsLoaded, StopFailure, CwdChanged, DirectoryAdded, FileChanged | None | No decision control. Used for side effects like logging or cleanup |
from line 3400
34013400
34023401Runs when an MCP server requests user input mid-task. By default, Claude Code shows an interactive dialog for the user to respond. Hooks can intercept this request and respond programmatically, skipping the dialog entirely.
34033402
3403For a complete hook with its settings entry and script, see [Answer a form request from a script](#answer-a-form-request-from-a-script).
3404
34043405The matcher field matches against the MCP server name.
34053406
34063407#### Elicitation input
from line 3445
34443445
34453446#### Elicitation output
34463447
3447To respond programmatically without showing the dialog, return a JSON object with `hookSpecificOutput`:
3448An Elicitation hook can answer the request for the user, decline or cancel it, or leave it to the dialog. To answer, decline, or cancel, exit 0 and print a `hookSpecificOutput` object with an `action`. The server gets your answer and no dialog appears. Each row of this table shows what to return for one outcome and what the MCP server receives:
34483449
3450| To | Return | The server receives |
3451| :- | :- | :- |
3452| Answer for the user | `"action": "accept"`, with the form field values in `content` | `accept` with your `content` |
3453| Decline the request | `"action": "decline"` | `decline` |
3454| Cancel the request | `"action": "cancel"` | `cancel` |
3455| Leave the request to the user | No output, with exit code 0 | The user's answer from the [dialog](/docs/en/mcp#respond-to-mcp-elicitation-requests) |
3456
3457This output answers the form-mode request shown under [Elicitation input](#elicitation-input). The keys in `content` are the property names from that request's `requested_schema`:
3458
34493459```json theme={null}
34503460{
34513461 "hookSpecificOutput": {
from line 3468
34583468}
34593469```
34603470
3461| Field | Values | Description |
3462| :- | :- | :- |
3463| `action` | `accept`, `decline`, `cancel` | Whether to accept, decline, or cancel the request |
3464| `content` | object | Form field values to submit. Only used when `action` is `accept` |
3471This output declines a request:
34653472
3466Exit code 2 denies the elicitation. Claude Code doesn't show your stderr message anywhere.
3473```json theme={null}
3474{
3475 "hookSpecificOutput": {
3476 "hookEventName": "Elicitation",
3477 "action": "decline"
3478 }
3479}
3480```
34673481
3468Claude Code acts on `hookSpecificOutput` from an Elicitation hook's JSON output and discards `systemMessage` and `continue`.
3482In the dialog, selecting **Decline** sends `decline` and pressing `Esc` sends `cancel`, so return the one you want the server to see.
34693483
3484For a URL-mode request, a hook that returns `accept` skips the dialog, so the URL never opens.
3485
3486Claude Code discards `reason`, `systemMessage`, and `continue` from an Elicitation hook's JSON output, whichever `action` you return.
3487
3488#### Other ways to decline an elicitation
3489
3490Your hook can also decline in these ways. The server receives the same `decline` as for `"action": "decline"`:
3491
3492* **Exits with code 2**: Claude Code ignores a `hookSpecificOutput` printed by the same hook
3493* **Prints a top-level `"decision": "block"`**: the block overrides an `action` in the same output
3494
3495When several hooks match the same request, a decline from one of them overrides an `accept` or `cancel` from another.
3496
3497This script declines URL-mode requests and leaves form requests to the dialog:
3498
3499```bash theme={null}
3500#!/bin/bash
3501if [ "$(jq -r '.mode')" = "url" ]; then
3502 exit 2
3503fi
3504```
3505
3506Neither the user nor the server sees why your hook declined, because Claude Code doesn't show your stderr or your `reason`.
3507
3508Claude Code ignored a top-level `decision` from `Elicitation` and `ElicitationResult` hooks from v2.1.105 until the fix in v2.1.284.
3509
3510#### Answer a form request from a script
3511
3512This example answers one recurring question for the user. An MCP server named `issue-tracker` asks for a project key in a form, and the hook fills in `DOCS`. The script accepts when `project_key` is the form's single field. For any other request it prints nothing, so the dialog appears.
3513
3514<Tabs>
3515 <Tab title="macOS/Linux">
3516 Register a command hook for the event in your settings file, with the server name as the matcher:
3517
3518 ```json theme={null}
3519 {
3520 "hooks": {
3521 "Elicitation": [
3522 {
3523 "matcher": "issue-tracker",
3524 "hooks": [
3525 {
3526 "type": "command",
3527 "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/answer-project-key.sh",
3528 "args": []
3529 }
3530 ]
3531 }
3532 ]
3533 }
3534 }
3535 ```
3536
3537 Save this script to `.claude/hooks/answer-project-key.sh` in your project and make it executable with `chmod +x`:
3538
3539 ```bash theme={null}
3540 #!/bin/bash
3541 input=$(cat)
3542 fields=$(jq -c '.requested_schema.properties // {} | keys' <<<"$input")
3543
3544 if [ "$fields" = '["project_key"]' ]; then
3545 jq -n '{hookSpecificOutput: {hookEventName: "Elicitation", action: "accept", content: {project_key: "DOCS"}}}'
3546 fi
3547 ```
3548 </Tab>
3549
3550 <Tab title="Windows (PowerShell)">
3551 Register a command hook that runs the script through PowerShell, with the server name as the matcher:
3552
3553 ```json theme={null}
3554 {
3555 "hooks": {
3556 "Elicitation": [
3557 {
3558 "matcher": "issue-tracker",
3559 "hooks": [
3560 {
3561 "type": "command",
3562 "command": "powershell.exe",
3563 "args": [
3564 "-NoProfile",
3565 "-ExecutionPolicy",
3566 "Bypass",
3567 "-File",
3568 "${CLAUDE_PROJECT_DIR}/.claude/hooks/answer-project-key.ps1"
3569 ]
3570 }
3571 ]
3572 }
3573 ]
3574 }
3575 }
3576 ```
3577
3578 Save this script to `.claude/hooks/answer-project-key.ps1` in your project:
3579
3580 ```powershell theme={null}
3581 $request = [Console]::In.ReadToEnd() | ConvertFrom-Json
3582 $fields = @($request.requested_schema.properties.PSObject.Properties.Name)
3583
3584 if ($fields.Count -eq 1 -and $fields[0] -eq 'project_key') {
3585 @{
3586 hookSpecificOutput = @{
3587 hookEventName = "Elicitation"
3588 action = "accept"
3589 content = @{ project_key = "DOCS" }
3590 }
3591 } | ConvertTo-Json -Depth 3
3592 }
3593 ```
3594 </Tab>
3595</Tabs>
3596
3597To confirm the hook works, start Claude Code with `claude --debug` and give Claude a task that makes the server ask for the project key. No dialog appears, and the [debug log](#debug-hooks) has a line that ends with `Elicitation resolved by hook: {"action":"accept","content":{"project_key":"DOCS"}}`.
3598
34703599### ElicitationResult
34713600
34723601Runs after a user responds to an MCP elicitation. Hooks can observe, modify, or block the response before it is sent back to the MCP server.
34733602
3603When an [Elicitation](#elicitation) hook answers a request, Claude Code sends that answer to the server without running ElicitationResult hooks.
3604
34743605The matcher field matches against the MCP server name.
34753606
34763607#### ElicitationResult input
from line 3617
34863617 "mcp_server_name": "my-mcp-server",
34873618 "action": "accept",
34883619 "content": { "username": "alice" },
3489 "mode": "form",
3490 "elicitation_id": "elicit-123"
3620 "mode": "form"
34913621}
34923622```
34933623
34943624#### ElicitationResult output
34953625
3496To override the user's response, return a JSON object with `hookSpecificOutput`:
3626An ElicitationResult hook can let the user's response through, change its values, or block it. To change or block the response, exit 0 and print a `hookSpecificOutput` object with an `action`. Each row of this table shows what to return for one outcome and what the MCP server receives:
34973627
3628| To | Return | The server receives |
3629| :- | :- | :- |
3630| Let the response through | No output, with exit code 0 | The user's response, unchanged |
3631| Change the submitted values | `"action": "accept"`, with the new values in `content` | `accept` with your `content` in place of the user's values |
3632| Block the response | `"action": "decline"` | `decline`, without the user's values |
3633| Cancel the request | `"action": "cancel"` | `cancel`, along with the values the user submitted. To withhold them, return `"decline"` |
3634
3635This output changes the response shown under [ElicitationResult input](#elicitationresult-input), so the server receives `[email protected]` where the user submitted `alice`:
3636
34983637```json theme={null}
34993638{
35003639 "hookSpecificOutput": {
35013640 "hookEventName": "ElicitationResult",
3502 "action": "decline",
3503 "content": {}
3641 "action": "accept",
3642 "content": {
3643 "username": "[email protected]"
3644 }
35043645 }
35053646}
35063647```
35073648
3508| Field | Values | Description |
3509| :- | :- | :- |
3510| `action` | `accept`, `decline`, `cancel` | Overrides the user's action |
3511| `content` | object | Overrides form field values. Only meaningful when `action` is `accept` |
3649Your `content` replaces the user's whole `content` object, so include the fields you aren't changing. Return `action` along with it, because Claude Code ignores a `hookSpecificOutput` that has no `action`.
35123650
3513Exit code 2 blocks the response, changing the effective action to `decline`. Claude Code doesn't show your stderr message anywhere.
3651ElicitationResult hooks also run when the user declines or cancels, and your `action` replaces theirs. Check that the input's `action` is `accept` before you return `accept`, or your hook turns a declined request into an accepted one. This script makes the same change when the user accepted, keeps the other fields, and prints nothing otherwise:
35143652
3515Claude Code acts on `hookSpecificOutput` from an ElicitationResult hook's JSON output and discards `systemMessage` and `continue`.
3653```bash theme={null}
3654#!/bin/bash
3655input=$(cat)
3656
3657if [ "$(jq -r '.action' <<<"$input")" = "accept" ]; then
3658 jq '{hookSpecificOutput: {hookEventName: "ElicitationResult", action: "accept", content: (.content + {username: (.content.username + "@example.com")})}}' <<<"$input"
3659fi
3660```
3661
3662This output blocks the response:
3663
3664```json theme={null}
3665{
3666 "hookSpecificOutput": {
3667 "hookEventName": "ElicitationResult",
3668 "action": "decline"
3669 }
3670}
3671```
3672
3673Exit code 2 and a top-level `"decision": "block"` also block the response. [Other ways to decline an elicitation](#other-ways-to-decline-an-elicitation) covers which one takes effect when a hook combines them, what the user sees, and which versions ignored `decision`.
3674
3675Claude Code discards `reason`, `systemMessage`, and `continue` from an ElicitationResult hook's JSON output, whichever `action` you return.
35163676
35173677## Prompt-based hooks
35183678
No line in this hunk matches that.