Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
"plugins": [
{
"name": "warp",
"description": "Native Warp notifications when Claude completes tasks or needs input",
"description": "Native Warp notifications with complete Claude Code hook lifecycle coverage",
"source": "./plugins/warp",
"version": "2.0.0",
"version": "2.0.1",
"category": "productivity",
"tags": ["notifications", "terminal", "warp"]
}
Expand Down
65 changes: 54 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,28 @@ Official [Warp](https://warp.dev) terminal integration for [Claude Code](https:/

Get native Warp notifications when Claude Code:
- **Completes a task** — with a summary showing your prompt and Claude's response
- **Completes a subagent** — when a nested Agent tool call finishes, with the subagent's response
- **Needs your input** — when Claude has been idle and is waiting for you
- **Requests permission** — when Claude wants to run a tool and needs your approval
- **Is auto-denied** — when the auto-mode classifier silently denies a tool call
- **Hits an API error** — rate limits, auth failures, billing errors surface in the sidebar instead of silently hanging
- **Asks a question via MCP** — MCP elicitation dialogs are routed through the same `question_asked` event OpenCode uses

Notifications appear in Warp's notification center and as system notifications, so you can context-switch while Claude works and get alerted when attention is needed.

### 📡 Session Status

The plugin keeps Warp informed of Claude's current state by emitting structured events on every session transition:
The plugin keeps Warp's sidebar in sync with Claude's lifecycle by emitting structured events on every transition:

- **Session start / end** — appear and disappear from the sidebar cleanly (`clear`, `resume`, `logout` reasons included)
- **Prompt submitted** — you sent a prompt, Claude is working
- **Tool completed** — a tool call finished, Claude is back to running
- **Permission request / denied** — awaiting approval, or silently denied by auto mode
- **Tool complete / failed** — distinguishes successful tool calls from errors
- **Subagent start / stop** — nested Agent runs visible in the sidebar instead of looking like one opaque tool call
- **Compact start / end** — context compaction is surfaced rather than appearing as a frozen session
- **Cwd changed** — sidebar project label updates in real time when Claude runs `cd`

This powers Warp's inline status indicators for Claude Code sessions.
Every event includes rich context: `permission_mode`, `model`, `source`, and event-specific payloads (tool previews, error types, subagent types) so Warp can render state with high fidelity.

## Installation

Expand All @@ -45,15 +55,46 @@ Once restarted, you'll see a confirmation message and notifications will appear

The plugin communicates with Warp via OSC 777 escape sequences. Each hook script builds a structured JSON payload (via `build-payload.sh`) and sends it to `warp://cli-agent`, where Warp parses it to drive notifications and session UI.

Payloads include a protocol version negotiated between the plugin and Warp (`min(plugin_version, warp_version)`), the session ID, working directory, and event-specific fields.
Payloads include a protocol version negotiated between the plugin and Warp (`min(plugin_version, warp_version)`), the session ID, working directory, and event-specific fields. Every handler has a 5-second timeout; `Stop`, `StopFailure`, and `SubagentStop` run async so the session response isn't blocked on tty writes.

### Hook inventory

The plugin registers sixteen hooks covering the full [Claude Code hook lifecycle](https://docs.anthropic.com/en/docs/claude-code/hooks):

| Claude Code hook | Warp event | Sidebar effect |
|---|---|---|
| `SessionStart` (`startup\|resume\|clear\|compact`) | `session_start` | registers/refreshes the sidebar entry; `model` + `source` surface in the UI |
| `SessionEnd` | `session_end` | archives the sidebar entry with termination reason |
| `UserPromptSubmit` | `prompt_submit` | transitions tab idle/done → running |
| `PermissionRequest` | `permission_request` | transitions tab → blocked-awaiting-permission with a rich summary |
| `PermissionDenied` | `permission_denied` | clears the blocked state when auto-mode classifier denies |
| `PostToolUse` (matcher: `Bash\|Edit\|Write\|MultiEdit\|NotebookEdit\|Agent`) | `tool_complete` | transitions tab → running with tool preview |
| `PostToolUseFailure` | `tool_failed` | distinguishes failed tool calls from successful ones |
| `SubagentStart` | `subagent_start` | surfaces nested Agent runs instead of flat "running" state |
| `SubagentStop` | `subagent_stop` | subagent's final response appears in the sidebar |
| `Notification` (`idle_prompt`) | `idle_prompt` | "waiting for input" badge |
| `Stop` | `stop` | transitions tab → done with prompt/response summary |
| `StopFailure` | `stop` (with `error` field) | API errors (rate limits, auth) surface instead of hanging |
| `PreCompact` / `PostCompact` | `compact_start` / `compact_end` | sidebar shows compaction instead of looking frozen |
| `CwdChanged` | `cwd_changed` | project label updates in real time on `cd` |
| `Elicitation` | `question_asked` | MCP elicitation routes through the existing OpenCode-compatible event |

### Payload envelope

Every payload carries the six-field common envelope:

```json
{
"v": 1,
"agent": "claude",
"event": "<event>",
"session_id": "<session-id>",
"cwd": "<absolute-path>",
"project": "<basename-of-cwd>"
}
```

The plugin registers six hooks:
- **SessionStart** — emits the plugin version and a welcome system message
- **Stop** — reads the transcript to extract your prompt and Claude's response, then sends a task-complete notification
- **Notification** (`idle_prompt`) — fires when Claude has been idle and needs your input
- **PermissionRequest** — fires when Claude wants to run a tool, includes the tool name and a preview of its input
- **UserPromptSubmit** — fires when you submit a prompt, signaling the session is active again
- **PostToolUse** — fires when a tool call completes, signaling the session is no longer blocked
Plus event-specific fields. Many events also include `permission_mode` (plan / acceptEdits / auto / bypassPermissions / default) so Warp can adapt sidebar rendering to the current mode. `SessionStart` additionally includes `model` and `source`.

### Legacy Support

Expand All @@ -76,6 +117,8 @@ Notifications work out of the box. To customize Warp's notification behavior (so
The plugin version in `plugins/warp/.claude-plugin/plugin.json` is checked by the Warp client to detect outdated installations.
When bumping the version here, also update `MINIMUM_PLUGIN_VERSION` in the Warp client.

Plugin v2.0.1 adds new Warp events (`session_end`, `permission_denied`, `tool_failed`, `subagent_start`, `subagent_stop`, `compact_start`, `compact_end`, `cwd_changed`) on top of v2.0.0's six-event baseline. Warp clients that don't know these events should silently ignore them; newer clients render them as first-class sidebar states. All existing v2.0.0 events are emitted unchanged — this is a backward-compatible patch.

## License

MIT License — see [LICENSE](LICENSE) for details.
4 changes: 2 additions & 2 deletions plugins/warp/.claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "warp",
"description": "Warp terminal integration for Claude Code - native notifications, and more to come",
"version": "2.0.0",
"description": "Warp terminal integration for Claude Code — complete hook lifecycle coverage, structured notifications, and sidebar session state",
"version": "2.0.1",
"author": {
"name": "Warp",
"url": "https://warp.dev"
Expand Down
144 changes: 132 additions & 12 deletions plugins/warp/hooks/hooks.json
Original file line number Diff line number Diff line change
@@ -1,34 +1,36 @@
{
"description": "Warp terminal notifications",
"description": "Warp terminal notifications — complete Claude Code hook lifecycle coverage",
"hooks": {
"SessionStart": [
{
"matcher": "startup|resume",
"matcher": "startup|resume|clear|compact",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/on-session-start.sh"
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/on-session-start.sh",
"timeout": 5
}
]
}
],
"Stop": [
"SessionEnd": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/on-stop.sh"
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/on-session-end.sh",
"timeout": 5
}
]
}
],
"Notification": [
"UserPromptSubmit": [
{
"matcher": "idle_prompt",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/on-notification.sh"
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/on-prompt-submit.sh",
"timeout": 5
}
]
}
Expand All @@ -38,27 +40,145 @@
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/on-permission-request.sh"
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/on-permission-request.sh",
"timeout": 5
}
]
}
],
"UserPromptSubmit": [
"PermissionDenied": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/on-prompt-submit.sh"
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/on-permission-denied.sh",
"timeout": 5
}
]
}
],
"PostToolUse": [
{
"matcher": "Bash|Edit|Write|MultiEdit|NotebookEdit|Agent",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/on-post-tool-use.sh",
"timeout": 5
}
]
}
],
"PostToolUseFailure": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/on-post-tool-use-failure.sh",
"timeout": 5
}
]
}
],
"SubagentStart": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/on-subagent-start.sh",
"timeout": 5
}
]
}
],
"SubagentStop": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/on-subagent-stop.sh",
"timeout": 5,
"async": true
}
]
}
],
"Notification": [
{
"matcher": "idle_prompt",
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/on-notification.sh",
"timeout": 5
}
]
}
],
"Stop": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/on-stop.sh",
"timeout": 5,
"async": true
}
]
}
],
"StopFailure": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/on-stop-failure.sh",
"timeout": 5,
"async": true
}
]
}
],
"PreCompact": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/on-pre-compact.sh",
"timeout": 5
}
]
}
],
"PostCompact": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/on-post-compact.sh",
"timeout": 5
}
]
}
],
"CwdChanged": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/on-cwd-changed.sh",
"timeout": 5
}
]
}
],
"Elicitation": [
{
"hooks": [
{
"type": "command",
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/on-post-tool-use.sh"
"command": "${CLAUDE_PLUGIN_ROOT}/scripts/on-elicitation.sh",
"timeout": 5
}
]
}
Expand Down
24 changes: 24 additions & 0 deletions plugins/warp/scripts/on-cwd-changed.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#!/bin/bash
# Hook script for Claude Code CwdChanged event
# Emits cwd_changed so the sidebar project label reflects cd commands executed
# by Claude. The envelope's `project` field is derived from basename(cwd) at
# notification time — this event pushes the update without waiting for the
# next tool call.
#
# https://docs.anthropic.com/en/docs/claude-code/hooks#cwdchanged

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/should-use-structured.sh"

if ! should_use_structured; then
exit 0
fi

source "$SCRIPT_DIR/build-payload.sh"

INPUT=$(cat)

# No extra fields — the envelope already carries the new cwd and derived project.
BODY=$(build_payload "$INPUT" "cwd_changed")

"$SCRIPT_DIR/warp-notify.sh" "warp://cli-agent" "$BODY"
33 changes: 33 additions & 0 deletions plugins/warp/scripts/on-elicitation.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/bin/bash
# Hook script for Claude Code Elicitation event
# MCP servers can request user input mid-tool-execution via elicitation.
# This maps directly to OpenCode's `question_asked` event — Warp already has
# UI wired for it, so re-using that event gets MCP elicitation into the
# sidebar with zero new event registration on Warp's side.
#
# https://docs.anthropic.com/en/docs/claude-code/hooks#elicitation-input

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/should-use-structured.sh"

if ! should_use_structured; then
exit 0
fi

source "$SCRIPT_DIR/build-payload.sh"

INPUT=$(cat)

# Elicitation's matcher is the MCP server name; it also appears in tool_name.
SERVER_NAME=$(echo "$INPUT" | jq -r '.server_name // .tool_name // "unknown"' 2>/dev/null)
MESSAGE=$(echo "$INPUT" | jq -r '.message // .requestedSchema.description // empty' 2>/dev/null)

if [ -n "$MESSAGE" ] && [ ${#MESSAGE} -gt 200 ]; then
MESSAGE="${MESSAGE:0:197}..."
fi

BODY=$(build_payload "$INPUT" "question_asked" \
--arg tool_name "$SERVER_NAME" \
--arg summary "$MESSAGE")

"$SCRIPT_DIR/warp-notify.sh" "warp://cli-agent" "$BODY"
Loading