How I Fixed Windows Notifications for Claude Code's Warp Plugin
A deep dive into debugging and fixing the Warp terminal plugin for Claude Code on Windows — from tracing OSC escape sequences to building native Windows toast notifications.
How I Fixed Windows Notifications for Claude Code's Warp Plugin
If you use Claude Code inside Warp terminal on Windows, you've probably noticed that notifications don't work. You finish a task, switch to another window, and... silence. No toast, no ping, nothing.
I spent an evening tracing the issue end-to-end and built a fix. Here's the full story.
The Setup
Claude Code has a plugin system that supports hooks — scripts that fire on events like task completion, idle prompts, or permission requests. The Warp plugin uses these hooks to send desktop notifications via OSC 777 escape sequences — a terminal protocol that Warp interprets to show native notifications.
On macOS and Linux, this works great. On Windows? Dead silence.
Finding the Root Cause
Step 1: Are the hooks even firing?
I added debug logging to the hook scripts:
echo "[WARP DEBUG] on-stop.sh fired at $(date)" >> /tmp/warp-debug.log
Result: Hooks were firing perfectly. Every stop event, every idle prompt — all logged.
Step 2: Where does the notification break?
The notification script (warp-notify.sh) sends the escape sequence like this:
printf '\033]777;notify;%s;%s\007' "$TITLE" "$BODY" > /dev/tty
On Windows (Git Bash), /dev/tty exists as a path but fails to open inside Claude Code's hook runner:
/dev/tty: No such device or address
Step 3: Why not stderr?
The fallback writes to stderr (>&2), but Claude Code's hook execution framework captures both stdout and stderr from hook subprocesses. The OSC sequence goes into Claude Code's internal buffer instead of reaching Warp's terminal emulator.
stdin → pipe (hook input JSON)
stdout → captured (for structured JSON responses)
stderr → captured (for error handling)
There's simply no file descriptor that routes to Warp's terminal on Windows.
The Fix: Native Windows Toast Notifications
Since we can't reach Warp's terminal emulator through the captured pipes, I bypassed the terminal entirely and used Windows native toast notifications via PowerShell.
win-toast.ps1
param(
[string]$Title = "Claude Code",
[string]$Body = "Task complete"
)
# Register Warp as the notification source
$appId = "dev.warp.Warp"
$regPath = "HKCU:\SOFTWARE\Classes\AppUserModelId\$appId"
$iconPath = "$env:LOCALAPPDATA\Programs\Warp\icon.ico"
if (-not (Test-Path $regPath)) {
New-Item -Path $regPath -Force | Out-Null
}
New-ItemProperty -Path $regPath -Name "DisplayName" -Value "Warp" `
-PropertyType String -Force | Out-Null
New-ItemProperty -Path $regPath -Name "IconUri" -Value $iconPath `
-PropertyType ExpandString -Force | Out-Null
# Send the toast
[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
[Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom, ContentType = WindowsRuntime] | Out-Null
$xml = New-Object Windows.Data.Xml.Dom.XmlDocument
$xml.LoadXml("<toast><visual><binding template='ToastGeneric'><text>$Title</text><text>$Body</text></binding></visual></toast>")
[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($appId).Show(
[Windows.UI.Notifications.ToastNotification]::new($xml)
)
The key trick: registering dev.warp.Warp as an AppUserModelId in the Windows registry so the notification appears branded as Warp with Warp's icon — not as "Windows PowerShell".
Deduplication
Multiple hooks fire for a single Claude Code event (Stop + Notification + PermissionRequest). On macOS, Warp stacks these in one panel. On Windows, each would be a separate toast popup.
Fix: a filesystem-based mutex using mkdir (atomic on all platforms):
LOCK_DIR="/tmp/warp-notify-lock"
if mkdir "$LOCK_DIR" 2>/dev/null; then
(sleep 8 && rmdir "$LOCK_DIR" 2>/dev/null) &
else
exit 0 # Another hook already fired
fi
Only the first hook within an 8-second window sends a notification.
Event-Specific Notifications
Different events get different notification styles:
| Event | Title | Example Body |
|---|---|---|
| Task done | ✅ Task Completed — project-name | Summary of what Claude did |
| Input needed | ⏳ Input Needed — project-name | Claude is waiting for your input |
| Permission | 🔐 Permission Required — project-name | Claude wants to run: Bash |
The project name comes from the hook's input JSON (cwd → basename), so you know which terminal tab needs attention.
Result
Native Windows toast notifications that:
- Show Warp's icon and branding
- Fire exactly once per event (no duplicates)
- Include project context and event type
- Work reliably from Claude Code's sandboxed hook runner
Filed Upstream
I filed issue #48 on the Warp plugin repo with the full diagnosis. The core problem is architectural — Claude Code captures stdio from hooks, and Windows lacks a /dev/tty equivalent that bypasses this. Until the plugin or Claude Code adds a Windows-native notification channel, the PowerShell toast approach is the best workaround.
Built while debugging with Claude Code itself — a nice bit of meta-debugging.