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
| Setup | Works in | What you get |
|---|---|---|
Bell + Windows Terminal bellStyle | Windows Terminal, native or WSL | A sound and a flashing taskbar icon |
| PowerShell toast hook | Native Windows; WSL through powershell.exe | A toast naming the project, nothing to install |
| BurntToast hook | Same, including PowerShell 7 | A toast; the module is no longer maintained |
msg.exe hook | Windows editions that include msg | A pop-up dialog |
| wsl-notify-send | WSL | A toast |
| OSC 777 from a hook | Windows Terminal Preview 1.26 | A 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:
- Shell form, a plain
"command"string, runs in Git Bash if it’s installed and in PowerShell if it isn’t. Set"shell": "powershell"to choose PowerShell; Claude Code then usespwsh.exe(PowerShell 7) when it finds it and falls back topowershell.exe(Windows PowerShell 5.1). - Exec form adds an
"args"array. Claude Code starts the program directly with those arguments, with no shell in between. On Windows the program has to be a real.exe, not a.cmdshim. - In WSL, Claude Code is a Linux program. Hooks run with
sh -cand it reads~/.claude/settings.jsoninside the distribution, not the one in your Windows profile.
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:
- It must be
powershell.exe. PowerShell 7 runs on modern .NET, which dropped built-in WinRT support, so the[Windows.UI.Notifications…]lines fail inpwsh. That’s also why this doesn’t use"shell": "powershell", which pickspwsh.exewhen it’s installed. - The app ID is the one Windows uses for the Windows PowerShell Start menu entry, so the toast appears as coming from Windows PowerShell. To confirm it on your machine, run
Get-StartApps -Name '*PowerShell*'. -NoProfileskips your PowerShell profile so the hook starts faster and anything your profile prints doesn’t end up in the hook’s output.
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
- Claude Code in the integrated terminal gets no built-in desktop notification. With
preferredNotifChannelset toterminal_bell, VS Code briefly shows a bell icon on the terminal tab;terminal.integrated.enableBellcontrols it. For a sound, add"accessibility.signals.terminalBell": { "sound": "on" }to your VS Code settings. - The Claude Code extension runs the same hooks from your settings files. There,
permission_promptfires about six seconds after Claude asks, even if you’re typing. Before v2.1.233 it didn’t fire in the extension at all, so update if your hook stays silent. - VS Code connected to WSL runs Claude Code inside the distribution, so use the WSL setup.
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 as | What goes wrong | Fix |
|---|---|---|
"C:\Users\you\notify.ps1" | \U isn’t a valid JSON escape, so the whole settings file fails to load | C:\\Users\\… or C:/Users/… |
"powershell.exe -Command \"Write-Output $env:USERNAME\"" | Git Bash expands $env to nothing before PowerShell sees it | Single quotes around the PowerShell code, or exec form |
Double quotes nested inside -Command "…" | Each layer strips one level; the inner string ends early | Single quotes inside, or a .ps1 file |
| A command copied from a web page or chat | Curly quotes (’ “ ”) aren’t quotes to a shell | Retype the quotes |
Bare $CLAUDE_PROJECT_DIR in a PowerShell command | PowerShell 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.
- Per hook:
-ExecutionPolicy Bypassin the arguments, as in the examples above, applies only to that one PowerShell process. - For your user:
Set-ExecutionPolicy -Scope CurrentUser RemoteSignedlets local scripts run everywhere. Scripts downloaded from the internet still need a signature orUnblock-File. - Work machines: run
Get-ExecutionPolicy -List. IfMachinePolicyorUserPolicyis set, Group Policy wins over both options above.
11. The hook runs but nothing shows
- Check the hook loaded. Type
/hooksin Claude Code.claude --debug hookslogs each run and its errors. - Run the script by hand with the test command in section 3. If that shows nothing either, the problem is Windows, not Claude Code.
- Notifications off. Open Settings → System → Notifications. Make sure notifications are on, find Windows PowerShell under Notifications from apps and other senders, and check it’s on with Show notification banners enabled. With banners off, toasts go straight to the notification center.
- Do Not Disturb. On Windows 11 it hides banners, and Focus sessions turn it on. On Windows 10 the same feature is Focus assist. Under Turn on do not disturb automatically, Windows can switch it on when you duplicate your display, play a game, use a full-screen app or in the first hour after a feature update, which is why toasts vanish while you’re screen sharing.
- A dialog you never saw. Message boxes can open behind the terminal. Check the taskbar.
- WSL errors.
not foundfor a.exemeans interop or the WindowsPATH; see section 6.
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:
- Each Claude Code, Codex, OpenCode and omp session becomes a pet that walks along the bottom of every page. When one needs permission, its pet bounces; click it and answer Allow, Always or Deny.
- In another app, a desktop notification names the pet that needs you, with Allow and Deny on it.
- Read the agent’s latest answer and send its next message without switching windows.
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.