macOS Only?
This is Part 3 of the Claude Code Notifier series. (4 parts total)
"Is that macOS only?"
That question changed everything.
Beginning
I wanted to share my Claude Code workflow with my team. Thought it was pretty good and others might find it useful. Making something that helps people feels good.
But when I showed it, the responses were:
"I use Ubuntu." "Our team uses WSL." "Would be nice if it worked on Windows..."
And so cross-platform support began.
Four Worlds
Four platforms to support:
macOS - what I use
Linux - what the backend team uses
Windows - what some use
WSL - what most use
macOS already worked. The other three were the problem.
Linux Was Easy
There's notify-send. Most Linux distros have it.
notify-send "Claude Code" "Task completed"
Done. Took five minutes.
Problem was when notify-send isn't installed. Some people don't have it. So I made it skip silently.
if ! command -v notify-send &> /dev/null; then
exit 0 # No notify-send, no problem
fi
Notifications are nice-to-have, not essential. Better to skip quietly than crash with an error.
Windows Wasn't Hard Either
Windows has Toast notifications. Can send them via PowerShell.
$xml = @"
<toast>
<visual>
<binding template="ToastText02">
<text id="1">Claude Code</text>
<text id="2">Task completed</text>
</binding>
</visual>
</toast>
"@
$toast = [Windows.UI.Notifications.ToastNotification]::new($xml)
$notifier = [Windows.UI.Notifications.ToastNotificationManager]::CreateToastNotifier("Claude Code")
$notifier.Show($toast)
XML is ugly but it works. Smooth sailing so far.
WSL Was the Problem
WSL. Windows Subsystem for Linux. Linux but not Linux. Windows but not Windows.
Thought it'd be simple. It's Linux, so notify-send should work, right?
Nope.
Where do notifications from notify-send go in WSL? Nowhere. WSL doesn't have a GUI by default. Had to use Windows notifications.
So I decided to call powershell.exe from WSL. WSL can run Windows programs.
powershell.exe -File ./windows.ps1
Didn't work.
Attempt 1: Paths
WSL and Windows have different path systems.
WSL: /home/user/data/file.txt
Windows: C:\Users\user\data\file.txt
Pass a WSL path to PowerShell and it can't find it. Need to convert to Windows path.
# wslpath does this
WIN_PATH=$(wslpath -w "$LINUX_PATH")
Solved quickly.
Attempt 2: Environment Variables
This was the real problem.
What happens when you set an env var in Bash and call PowerShell?
export MY_VAR="hello"
powershell.exe -Command 'echo $env:MY_VAR'
Nothing. Empty.
Environment variables don't pass through when calling Windows programs from WSL. Didn't know this. Spent three hours.
"Why is it empty?" "I definitely set it." "What's wrong?"
Found the answer on Stack Overflow. There's something called WSLENV.
export WSLENV="MY_VAR"
export MY_VAR="hello"
powershell.exe -Command 'echo $env:MY_VAR'
Now it works.
List variable names in WSLENV and only those pass to Windows. No idea why this isn't automatic.
export WSLENV="MIN_DURATION_SECONDS:NOTIFY_MESSAGE:NOTIFICATION_TYPE:DATA_DIR_WIN"
Colon-separated. Ugly. But it works.
Attempt 3: Passing Data
Had to pass JSON data from hooks to PowerShell.
First tried command line arguments.
powershell.exe -Command "& { param($j) ... }" -j "$JSON"
Blew up on special characters. JSON has quotes, braces, colons everywhere. Escape hell.
Tried environment variables.
export JSON_DATA="$JSON"
export WSLENV="JSON_DATA"
Hit size limits. Long JSON got truncated.
Finally solved it with stdin.
echo "$JSON" | powershell.exe -File ./windows.ps1
$json = [Console]::In.ReadToEnd()
Cleanest solution. Should've done this from the start.
OS Detection
Had to distinguish four platforms. Order matters.
# 1. Windows Native (Git Bash)
if [ "$OS" = "Windows_NT" ]; then
# Windows handling
# 2. WSL - must come before Linux!
elif grep -qi microsoft /proc/version 2>/dev/null; then
# WSL handling
# 3. macOS
elif [ "$(uname)" = "Darwin" ]; then
# macOS handling
# 4. Linux
else
# Linux handling
fi
WSL must be checked before Linux. Running uname in WSL returns Linux. Wrong order means WSL gets misclassified as Linux.
Took an hour. "Why are Linux notifications going in WSL?"
Result
┌─────────────┬──────────────────┐
│ Platform │ Notification │
├─────────────┼──────────────────┤
│ macOS │ osascript │
│ Linux │ notify-send │
│ Windows │ Toast (WinRT) │
│ WSL │ Toast via PS │
└─────────────┴──────────────────┘
Four platforms, all working.
Shared it on our team chat. Got thumbs up. That felt good.
Lessons
-
WSL is special. Looks like Linux but isn't. Needs separate handling.
-
Remember WSLENV. Required for passing env vars from WSL to Windows.
-
stdin is the answer. Pipe complex data. Better than args or env vars.
-
Fail silently. If notification fails, just skip. Don't crash with error messages.
-
Detection order matters. WSL first, Linux last.
So
"Is that macOS only?"
Not anymore.
Next post covers submitting this as an official plugin PR.
Series:
- Part 1: Notify Me When Claude Code Finishes
- Part 2: It's All About Hooks
- Part 3: macOS Only? (current)
- Part 4: Create pull request
