Claude Code notifications on Windows: get told when it finishes or needs you

In Windows Terminal, Claude Code does not notify you by default. You find out it was waiting for permission ten minutes after it started waiting. Here are two fixes, one of them a tested PowerShell script.

THE SHORT VERSION
  • Claude Code's default notification setting does nothing in Windows Terminal. That is documented, not a bug on your machine.
  • Quick fix: switch Claude Code to the terminal bell and make Windows Terminal flash its taskbar button.
  • Proper fix: a hook that runs a short PowerShell script and shows a real Windows notification. The script below needs no extra modules and was tested on Windows 11.
  • Run the script with powershell.exe, not PowerShell 7. The newer one cannot load the Windows notification classes.

You give Claude Code a task, switch to your browser, and come back fifteen minutes later to find it stopped after thirty seconds to ask whether it may run npm install. On a Mac in iTerm2 you would have had a notification. On Windows you got nothing.

Here is why, and two ways to fix it. Everything is based on Anthropic's settings and hooks documentation and Microsoft's Windows Terminal docs as of 4 October 2026.

We ran into this problem while building AigoPilot, a Claude Code GUI for Windows that sends these notifications for every Claude Code and Codex session. You do not need it to fix the problem: both fixes below work on their own.

Why Windows stays quiet

Claude Code has a setting called preferredNotifChannel, shown as "Local notifications" in /config. Its default is auto. In iTerm2, Ghostty and Kitty, auto sends a desktop notification. In Apple's Terminal it rings the bell in some cases. In every other terminal, Anthropic's docs say, it does nothing. Windows Terminal is one of those others.

So there are two ways out: ask Claude Code to ring the terminal bell and let Windows Terminal turn that into something visible, or attach your own notification with a hook.

Fix 1: the terminal bell and a flashing taskbar

This takes a minute and needs no scripts. In Claude Code, run /config and set Local notifications to terminal_bell, or put this in %USERPROFILE%\.claude\settings.json:

{
  "preferredNotifChannel": "terminal_bell"
}

Then tell Windows Terminal what to do with a bell. Open its settings (Ctrl + ,), choose "Open JSON file" at the bottom left, and add bellStyle to the profile defaults:

"profiles": {
  "defaults": {
    "bellStyle": "all"
  }
}

With all, a bell plays a sound and flashes the Windows Terminal button on the taskbar; when the window is in the background, only the taskbar flashes. The other values are audible (the default), window, taskbar and none.

The limit of this fix: a flashing taskbar button tells you something happened, not what, and not in which of your four terminal tabs.

Fix 2: a real Windows notification from a hook

Hooks are commands Claude Code runs when something happens. Two events matter here:

  • Notification fires when Claude Code wants your attention. Its permission_prompt type fires once a permission prompt has waited about six seconds, and idle_prompt fires about sixty seconds after Claude finished, if you have not typed since. In a terminal, both fire only when you seem to be away.
  • Stop fires every time Claude finishes a reply.

Hooks run alongside the built-in setting, so you can keep Fix 1 as well.

Step 1: save the script

Save this as notify.ps1 in your %USERPROFILE%\.claude folder. It reads what Claude Code sends the hook, builds a two-line notification and shows it through Windows' own notification system. No module to install.

# notify.ps1: shows a Windows notification for Claude Code.
# Run it with Windows PowerShell (powershell.exe), not PowerShell 7.
[Console]::InputEncoding = [Text.Encoding]::UTF8
$data = [Console]::In.ReadToEnd() | ConvertFrom-Json

$project = Split-Path $data.cwd -Leaf
if ($data.hook_event_name -eq 'Stop') {
  $title = "Claude Code finished"
  $text = "The session in $project is waiting for your next prompt."
} else {
  $title = "Claude Code needs you"
  $text = "$($data.message) ($project)"
}

[Windows.UI.Notifications.ToastNotificationManager, Windows.UI.Notifications, ContentType = WindowsRuntime] | Out-Null
[Windows.Data.Xml.Dom.XmlDocument, Windows.Data.Xml.Dom.XmlDocument, ContentType = WindowsRuntime] | Out-Null

$xml = New-Object Windows.Data.Xml.Dom.XmlDocument
$xml.LoadXml(@"
<toast><visual><binding template="ToastGeneric">
<text>$([Security.SecurityElement]::Escape($title))</text>
<text>$([Security.SecurityElement]::Escape($text))</text>
</binding></visual></toast>
"@)

# Windows PowerShell's own app id, so the notification has a sender Windows already knows.
$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)

The notification appears under the name "Windows PowerShell". That is the sender the script borrows, because Windows only shows notifications from programs it knows.

Step 2: try it by hand

Before wiring it to Claude Code, check that Windows shows it. In PowerShell:

'{"hook_event_name":"Notification","message":"Test from PowerShell","cwd":"C:\\demo"}' | powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$env:USERPROFILE\.claude\notify.ps1"

A notification titled "Claude Code needs you" should slide in at the bottom right. If nothing appears, check that notifications and Do not disturb are set the way you expect in Windows Settings, under System, Notifications.

Step 3: add the hooks

Open %USERPROFILE%\.claude\settings.json and add a hooks section. Replace YOUR-NAME with your Windows user folder name. The path uses forward slashes on purpose: they work whether Claude Code runs the hook through Git Bash or through PowerShell.

{
  "hooks": {
    "Notification": [
      {
        "matcher": "permission_prompt",
        "hooks": [{ "type": "command", "command": "powershell.exe -NoProfile -ExecutionPolicy Bypass -File C:/Users/YOUR-NAME/.claude/notify.ps1" }]
      },
      {
        "matcher": "idle_prompt",
        "hooks": [{ "type": "command", "command": "powershell.exe -NoProfile -ExecutionPolicy Bypass -File C:/Users/YOUR-NAME/.claude/notify.ps1" }]
      }
    ],
    "Stop": [
      {
        "hooks": [{ "type": "command", "command": "powershell.exe -NoProfile -ExecutionPolicy Bypass -File C:/Users/YOUR-NAME/.claude/notify.ps1" }]
      }
    ]
  }
}

If your settings.json already has content, merge the hooks key into it rather than pasting a second set of braces. Restart Claude Code, or open /hooks to check that it sees them.

Too many notifications?

Stop fires after every reply, including the ones you were watching. If that gets noisy, delete the Stop block and keep idle_prompt, which only fires when a finished session has sat untouched for about a minute. If six seconds is too long to wait for permission prompts, the PermissionRequest event fires the moment a tool call needs your decision.

When it does not work

  • Nothing happens, no error. Run claude --debug; the log in %USERPROFILE%\.claude\debug\ shows each hook that ran and what it printed.
  • An error that names Windows.UI.Notifications. The script probably ran in PowerShell 7 (pwsh). .NET 5 and later removed built-in support for these Windows classes. Call powershell.exe, as in the settings above.
  • Odd characters in the message. Make sure the first line of the script, which sets UTF-8 input, is still there.
  • The hook breaks under Git Bash. Git Bash may load your shell profile, and anything a profile prints can get in the way. The command above starts PowerShell directly, which avoids most of that.
  • Claude Code runs in WSL. The same idea works if Windows interop is on, so that powershell.exe can be found from Linux. Point -File at the Windows path of the script.

What about BurntToast?

Most older guides use the BurntToast PowerShell module: Install-Module -Name BurntToast, then New-BurntToastNotification -Text 'Claude Code', 'Needs you'. It still installs and works. But its repository was archived and the README says it is no longer maintained, which is why the script above does the same job without it.

Other ways to be told

  • The Claude desktop app. Its Code tab sends a Windows notification when a session finishes and you are not looking at it.
  • The VS Code extension. The tab shows a blue dot when a permission request waits and an orange dot when Claude finished in the background. Useful, but only while the editor is in front of you.
  • An app that watches every session. Once you run sessions in several projects, one script per machine works, but every notification looks the same. That is where a Claude Code GUI for Windows starts to pay off.

Questions people ask

Why doesn't Claude Code notify me on Windows?

Its default notification setting, auto, sends desktop notifications only in iTerm2, Ghostty and Kitty and rings the bell in some cases in Terminal.app. In other terminals, including Windows Terminal, it does nothing.

Which hook should I use for a Claude Code notification?

Use Notification with the permission_prompt and idle_prompt matchers for 'needs you' alerts, and Stop if you also want an alert every time Claude finishes a reply. PermissionRequest fires at once if you want no delay at all.

Why does my toast script work in Windows PowerShell but not in PowerShell 7?

The script uses Windows Runtime types, and .NET 5 and later dropped built-in support for them. Run it with powershell.exe, which is Windows PowerShell 5.1, not pwsh.exe.

Is BurntToast still a good choice?

It still installs and works, but its repository was archived and it is no longer maintained. The script in this article needs no module at all.

Sources