2026-05-16

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.

claude-codewarpwindowsdeveloper-toolsdebugging

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:

EventTitleExample Body
Task done✅ Task Completed — project-nameSummary of what Claude did
Input needed⏳ Input Needed — project-nameClaude is waiting for your input
Permission🔐 Permission Required — project-nameClaude wants to run: Bash

The project name comes from the hook's input JSON (cwdbasename), 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.

Related Reading

Subscribe to my newsletter

No spam, promise. I only send curated blogs that match your interests — the stuff you'd actually want to read.

Interests (optional)

Unsubscribe anytime. Your email is safe with me.