Summary
Releases/v5.0.0/.claude/PAI/PULSE/VoiceServer/voice.ts:375-388 showDesktopNotification() unconditionally spawns /usr/bin/osascript (macOS-only). On Linux / WSL2 every voice notification fires a posix_spawn '/usr/bin/osascript' ENOENT in the daemon log. With Pulse cron jobs producing voice output, journal spam can reach one ENOENT every few seconds, drowning real signal.
Repro
# Linux/WSL2, PAI v5.0.0:
curl -X POST http://localhost:31337/notify \
-H 'Content-Type: application/json' \
-d '{"message":"test","voice_enabled":true}'
# Watch the journal:
journalctl --user -u pai-pulse --since "30 seconds ago" | grep osascript
# Output:
# error: ENOENT: no such file or directory, posix_spawn '/usr/bin/osascript'
The TTS HTTP request still returns 200 OK and audio bytes are generated, but the desktop-notification path crashes.
Current code (upstream main)
// voice.ts:375-388
async function showDesktopNotification(title: string, message: string): Promise<void> {
// ... no platform check
const proc = spawn("/usr/bin/osascript", ["-e", script])
proc.on("exit", (code) => (code === 0 ? resolve() : reject(new Error(`osascript exited ${code}`))))
}
Expected
On non-darwin platforms, showDesktopNotification should either:
- Early-return silently (no-op — voice itself still plays through
playAudio), OR
- Branch to a Linux equivalent —
notify-send on Linux desktop, powershell.exe -c '...' shim on WSL2.
Early-return is the smallest reversible change and matches what PR #1116 does on the adjacent playAudio path for browser-audio-streaming.
Suggested fix (locally applied — 1 line)
async function showDesktopNotification(title: string, message: string): Promise<void> {
+ if (process.platform !== "darwin") return
const escapedTitle = title.replace(/"/g, '\\"')
...
const proc = spawn("/usr/bin/osascript", ["-e", script])
Tested on WSL2 + systemd-user. ENOENT spam stopped immediately after restart; voice TTS audio playback (via locally-patched playAudio → mpv) continued working without notification.
Environment
- PAI v5.0.0 (file unchanged in current main as of 2026-05-12)
- Ubuntu 24.04 on WSL2, systemd-user supervisor
Related
Summary
Releases/v5.0.0/.claude/PAI/PULSE/VoiceServer/voice.ts:375-388showDesktopNotification()unconditionally spawns/usr/bin/osascript(macOS-only). On Linux / WSL2 every voice notification fires aposix_spawn '/usr/bin/osascript' ENOENTin the daemon log. With Pulse cron jobs producing voice output, journal spam can reach one ENOENT every few seconds, drowning real signal.Repro
The TTS HTTP request still returns
200 OKand audio bytes are generated, but the desktop-notification path crashes.Current code (upstream main)
Expected
On non-darwin platforms,
showDesktopNotificationshould either:playAudio), ORnotify-sendon Linux desktop,powershell.exe -c '...'shim on WSL2.Early-return is the smallest reversible change and matches what PR #1116 does on the adjacent
playAudiopath for browser-audio-streaming.Suggested fix (locally applied — 1 line)
async function showDesktopNotification(title: string, message: string): Promise<void> { + if (process.platform !== "darwin") return const escapedTitle = title.replace(/"/g, '\\"') ... const proc = spawn("/usr/bin/osascript", ["-e", script])Tested on WSL2 + systemd-user. ENOENT spam stopped immediately after restart; voice TTS audio playback (via locally-patched
playAudio→ mpv) continued working without notification.Environment
Related
.cursor/rules/ELOOP (also Linux-port issue)Bun.spawn(["bash", ...])ENOENT (same general class of macOS-assumption bug)