Claude Code notifications on Windows and WSL

Updated 24 October 2026

On Windows, Claude Code doesn’t send a desktop notification on its own. Its built-in notifications only work in Ghostty, Kitty and iTerm2, so in Windows Terminal, the VS Code terminal or a WSL shell, a permission prompt waits silently until you look. This guide covers each way to get an alert on Windows, from a taskbar flash to a proper toast, plus the quoting and execution policy problems that stop most Windows hooks from working the first time.

The short answer

SetupWorks inWhat you get
Bell + Windows Terminal bellStyleWindows Terminal, native or WSLA sound and a flashing taskbar icon
PowerShell toast hookNative Windows; WSL through powershell.exeA toast naming the project, nothing to install
BurntToast hookSame, including PowerShell 7A toast; the module is no longer maintained
msg.exe hookWindows editions that include msgA pop-up dialog
wsl-notify-sendWSLA toast
OSC 777 from a hookWindows Terminal Preview 1.26A toast from Terminal itself

1. How Claude Code runs hooks on Windows

Every option except the bell is a hook: a command Claude Code runs when it needs you, with the event’s details as JSON on stdin. On Windows, which shell runs that command decides how you have to write it:

On native Windows, your user settings live at %USERPROFILE%\.claude\settings.json. The setup below uses exec form with a script file, because it sidesteps nearly every quoting problem in section 9.

2. Windows Terminal: bell and taskbar flash

The least setup is the terminal bell. Tell Claude Code to ring it when it needs you:

%USERPROFILE%\.claude\settings.json{
  "preferredNotifChannel": "terminal_bell"
}

Windows Terminal’s default bellStyle is "audible", which only plays a sound. To also flash the taskbar icon when Terminal isn’t in focus, open Settings, click Open JSON file and set it for every profile:

Windows Terminal settings.json{
  "profiles": {
    "defaults": {
      "bellStyle": ["audible", "taskbar"]
    }
  }
}

Merge "bellStyle" into the "defaults" object you already have rather than pasting a second "profiles" key. "window" flashes the window as well, "all" turns on everything and "none" silences it. Because it’s a Terminal setting, it covers WSL profiles too.

In Windows Terminal Preview 1.26, bellStyle also accepts "notification", which shows a Windows notification when the bell rings. It’s off by default, and the stable 1.25 release doesn’t have it. With ["taskbar", "notification"] and the bell setting above, you get a toast without writing a hook.

3. A toast with built-in PowerShell

Windows PowerShell 5.1 ships with every copy of Windows 10 and 11 and can call the Windows toast API directly, so this needs nothing installed. Save the script as %USERPROFILE%\.claude\hooks\notify.ps1:

notify.ps1# Claude Code hook: show a Windows toast. Needs Windows PowerShell 5.1 (powershell.exe), not pwsh.
$hook = [Console]::In.ReadToEnd() | ConvertFrom-Json
$project = Split-Path -Leaf $hook.cwd
$message = if ($hook.message) { $hook.message } else { 'Claude Code needs your attention' }

$null = [Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime]
$xml = [Windows.UI.Notifications.ToastNotificationManager]::GetTemplateContent(
  [Windows.UI.Notifications.ToastTemplateType]::ToastText02)
$text = $xml.GetElementsByTagName('text')
$null = $text.Item(0).AppendChild($xml.CreateTextNode("Claude Code: $project"))
$null = $text.Item(1).AppendChild($xml.CreateTextNode($message))

$appId = '{1AC14E77-02E7-4E5D-B744-2EB1AE5198B7}\WindowsPowerShell\v1.0\powershell.exe'
$toast = [Windows.UI.Notifications.ToastNotification]::new($xml)
[Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier($appId).Show($toast)

Then register it, replacing you with your Windows user name. Forward slashes are fine in the path and save you from doubling every backslash:

%USERPROFILE%\.claude\settings.json{
  "hooks": {
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "powershell.exe",
            "args": ["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", "C:/Users/you/.claude/hooks/notify.ps1"]
          }
        ]
      }
    ]
  }
}

Three details in there matter:

Test it without Claude Code by piping in a fake event from a PowerShell prompt:

PowerShell'{"cwd":"C:\\code\\demo","message":"test"}' | powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$HOME\.claude\hooks\notify.ps1"

An empty matcher runs on every notification type. Use permission_prompt to be told only about approvals. Like the bell, a Notification hook waits until you haven’t typed for about six seconds; register the same handler under PermissionRequest for an alert the moment Claude asks. On Windows without Git Bash, a PermissionRequest hook that fails can block the tool call, so add "onFailure": "continue" to that handler (Claude Code v2.1.295 or later).

4. BurntToast

BurntToast is a PowerShell module that wraps the toast API in one command, and it works in PowerShell 7 as well as 5.1. Its repository is archived: v1.1.0 is the last release and there won’t be fixes. It still works, but the built-in script above has nothing to go stale. Install it for your user:

PowerShellInstall-Module -Name BurntToast -Scope CurrentUser

Answer yes if it asks about the untrusted PSGallery repository. The hook script shrinks to this:

toast.ps1$hook = [Console]::In.ReadToEnd() | ConvertFrom-Json
$project = Split-Path -Leaf $hook.cwd
$message = if ($hook.message) { $hook.message } else { 'Claude Code needs your attention' }
New-BurntToastNotification -Text "Claude Code: $project", $message

Register it the same way as notify.ps1; with BurntToast you can use pwsh.exe instead of powershell.exe in "command". Adding -Urgent marks the toast as an important notification, which can break through Focus Assist.

5. msg.exe: blunt but immediate

msg sends a message box to a Windows session. It’s ugly, it can’t carry the project name without a script, and the box stays on screen for 60 seconds by default, but it’s hard to miss and needs no PowerShell at all:

%USERPROFILE%\.claude\settings.json{
  "hooks": {
    "Notification": [
      {
        "matcher": "permission_prompt",
        "hooks": [
          { "type": "command", "command": "msg.exe", "args": ["*", "Claude Code needs your permission"] }
        ]
      }
    ]
  }
}

* sends the message to every user on the machine, which on a personal PC is just you. Exec form matters here: in a Git Bash shell-form command, an unquoted * would expand to the file names in the project folder. Not every Windows edition ships msg.exe; check with where.exe msg. Claude Code’s own docs use a System.Windows.Forms message box instead, which has a similar catch: the dialog can open behind your terminal.

6. WSL

If you run Claude Code inside WSL, hooks are Linux commands, but WSL interop lets them start Windows programs. Both options below rely on it, and it’s on by default: /etc/wsl.conf would have to set enabled=false or appendWindowsPath=false under [interop] to turn it off.

Call the Windows toast script

Keep notify.ps1 on your Windows drive and call it from the WSL settings file. The hook’s JSON is piped through to PowerShell, and Split-Path handles the Linux-style cwd fine:

~/.claude/settings.json (in WSL){
  "hooks": {
    "Notification": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "powershell.exe -NoProfile -ExecutionPolicy Bypass -File 'C:/Users/you/.claude/hooks/notify.ps1'"
          }
        ]
      }
    ]
  }
}

If you get powershell.exe: not found, your Windows folders aren’t on the WSL PATH. Use the full path, /mnt/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe.

wsl-notify-send

wsl-notify-send is a small Windows program that stands in for Linux’s notify-send. Download wsl-notify-send.exe from its releases page and put it in a folder on your Windows PATH. Two catches: it takes exactly one message argument, so notify-send 'Title' 'Body' style calls just print its usage, and the title goes in --category. And the notify-send() shell function its README suggests lives in your .bashrc, which hooks don’t load, so call the .exe directly:

~/.claude/hooks/notify.sh (in WSL)#!/bin/bash
input=$(cat)
project=$(basename "$(jq -r '.cwd' <<<"$input")")
message=$(jq -r '.message // "Claude Code needs your attention"' <<<"$input")
wsl-notify-send.exe --category "Claude Code: $project" "$message"

Make it executable with chmod +x ~/.claude/hooks/notify.sh, install jq with sudo apt install jq, and use ~/.claude/hooks/notify.sh as the hook’s "command".

7. A toast straight from Windows Terminal (Preview)

Windows Terminal Preview 1.26 can turn the OSC 777 escape sequence into a desktop notification. Hooks can’t write to the terminal themselves, but they can return the sequence in a terminalSequence field and Claude Code prints it for them. First allow it in Terminal’s settings:

Windows Terminal settings.json{
  "profiles": {
    "defaults": {
      "compatibility.allowOSC777": true
    }
  }
}

Then have the hook print the JSON. On native Windows, register this script the same way as notify.ps1; it works in either PowerShell:

osc.ps1$hook = [Console]::In.ReadToEnd() | ConvertFrom-Json
$message = if ($hook.message) { $hook.message } else { 'Claude Code needs your attention' }
$esc = [char]27
$bel = [char]7
@{ terminalSequence = "$esc]777;notify;Claude Code;$message$bel" } | ConvertTo-Json -Compress

In WSL, the bash version from Claude Code’s hooks reference does the same thing:

~/.claude/hooks/osc.sh (in WSL)#!/bin/bash
input=$(cat)
body=$(jq -r '.message // "Needs your attention"' <<<"$input")
seq=$(printf '\033]777;notify;%s;%s\007' "Claude Code" "$body")
jq -nc --arg seq "$seq" '{terminalSequence: $seq}'

Claude Code only writes the sequence in an interactive session while its interface is on screen, and the script must print nothing but that one JSON object.

8. VS Code on Windows

9. Quoting: JSON, then a shell, then PowerShell

A one-line shell-form command is parsed three times: as a JSON string, by the shell Claude Code starts (Git Bash or PowerShell), and again by powershell.exe -Command. The failures that come from that:

Written asWhat goes wrongFix
"C:\Users\you\notify.ps1"\U isn’t a valid JSON escape, so the whole settings file fails to loadC:\\Users\\… or C:/Users/…
"powershell.exe -Command \"Write-Output $env:USERNAME\""Git Bash expands $env to nothing before PowerShell sees itSingle quotes around the PowerShell code, or exec form
Double quotes nested inside -Command "…"Each layer strips one level; the inner string ends earlySingle quotes inside, or a .ps1 file
A command copied from a web page or chatCurly quotes (’ “ ”) aren’t quotes to a shellRetype the quotes
Bare $CLAUDE_PROJECT_DIR in a PowerShell commandPowerShell reads it as an unset variable$env:CLAUDE_PROJECT_DIR or ${CLAUDE_PROJECT_DIR}

Exec form with a .ps1 file avoids all of these: JSON is the only layer, and each argument reaches PowerShell exactly as written.

10. Execution policy

If the hook fails with cannot be loaded because running scripts is disabled on this system, PowerShell’s execution policy is blocking your .ps1. On Windows clients the effective policy is Restricted unless you or your organization changed it. Restricted allows typed commands but not script files, and that includes module files, so BurntToast won’t load either.

11. The hook runs but nothing shows

For sounds instead of toasts, see how to play a sound when Claude Code finishes.

Answer from your browser on Windows with Agent Pets

Every option above tells you to go back to the terminal. Agent Pets runs on Windows in Chrome, Edge, Brave and Arc, and lets you answer from the page you’re on:

Run npx getagentpets init to add its hooks next to any you have, then npx getagentpets start to run the helper; on Windows you start it yourself and update with npx getagentpets update. It needs Node.js 18 or later and runs entirely on your computer.

Get Agent Pets — was $25, now $6Get Agent Pets — $25

Questions

Why doesn’t Claude Code show a notification in Windows Terminal?

Claude Code only sends built-in desktop notifications in Ghostty, Kitty and iTerm2. In Windows Terminal, set preferredNotifChannel to terminal_bell and bellStyle to include taskbar, or add a Notification hook that shows a toast.

How do I show a Windows toast from a Claude Code hook?

Save a PowerShell script that reads the hook JSON and shows a toast, then run it with "command": "powershell.exe" and args -NoProfile, -ExecutionPolicy Bypass and -File with the script path. The built-in toast API needs Windows PowerShell 5.1; BurntToast works in PowerShell 7 too.

How do I get Claude Code notifications in WSL?

Hooks in WSL run in Linux, but they can start Windows programs. Call powershell.exe with a toast script on your Windows drive, or use wsl-notify-send.exe. Both need WSL interop, which is on by default.

Why does my hook say running scripts is disabled on this system?

PowerShell’s execution policy blocks .ps1 files, and on Windows clients it’s Restricted unless someone changed it. Add -ExecutionPolicy Bypass to the hook’s arguments, or run Set-ExecutionPolicy -Scope CurrentUser RemoteSigned. A Group Policy setting overrides both.

Why does the hook run but no toast appears?

Usually Do Not Disturb, called Focus assist on Windows 10, or notifications turned off for Windows PowerShell. Check Settings > System > Notifications. Do Not Disturb can also turn on by itself when you duplicate your display, play a game or use a full-screen app.

Sources: Claude Code’s documentation on hooks, the hooks guide and terminal configuration; Microsoft’s docs on Windows Terminal profile settings, execution policies, msg, Get-StartApps, ToastTemplateType, WinRT support in .NET 5, WSL configuration and notifications and Do Not Disturb; the Windows Terminal release notes; BurntToast; wsl-notify-send; VS Code’s docs on terminal appearance.

More guides