Skip to content

Bug: macOS desktop notifications silently fail — osascript display notification swallowed when terminal app lacks notification permissions #2632

@jokerkeny

Description

@jokerkeny

Bug: macOS desktop notifications silently fail — osascript display notification swallowed when terminal app lacks notification permissions

GSD Version

2.40+ (affects all versions using notifications.js)

Affected area

Auto-mode / Notifications

What happened?

GSD auto-mode notifications (milestone complete, budget warnings, blockers, errors) never appear on macOS despite notifications.enabled: true in preferences.

The root cause: notifications.js uses osascript -e 'display notification ...' on macOS. This command exits 0 (no error) but the notification is silently dropped by macOS if the calling terminal app (e.g. Ghostty, iTerm2, Alacritty, Kitty, Warp) doesn't have notification permissions in System Settings → Notifications.

Most terminal apps don't appear in the Notifications settings panel until they've successfully delivered at least one notification — creating a chicken-and-egg problem where the permission entry never shows up because notifications are silently swallowed before they can register.

Steps to reproduce

  1. Use any third-party terminal (Ghostty, iTerm2, etc.) that doesn't have notification permissions enabled
  2. Set notifications.enabled: true in GSD preferences
  3. Run /gsd auto on a milestone
  4. Wait for milestone completion or budget threshold
  5. No notification appears — no error logged, no indication of failure

Verify manually:

# This exits 0 but produces no visible notification:
osascript -e 'display notification "test" with title "GSD" sound name "Glass"'

# This works because terminal-notifier registers as its own app:
brew install terminal-notifier
terminal-notifier -title "GSD" -message "test" -sound Glass

Expected behavior

Desktop notifications should be delivered reliably when enabled in preferences.

Proposed fix

Prefer terminal-notifier when available on macOS, fall back to osascript:

// In buildDesktopNotificationCommand() for platform === "darwin":
// 1. Check for terminal-notifier first (registers as its own Notification Center app)
const tnPath = which("terminal-notifier"); // or execFileSync("which", ["terminal-notifier"])
if (tnPath) {
    const args = ["-title", title, "-message", message];
    if (level === "error") args.push("-sound", "Basso");
    else args.push("-sound", "Glass");
    return { file: "terminal-notifier", args };
}
// 2. Fall back to osascript (works if Terminal.app/Script Editor has notification perms)

terminal-notifier (brew install terminal-notifier) registers itself as a standalone app in Notification Center, so it gets its own permission entry on first use — macOS prompts the user to allow/deny, which is the expected UX.

Workaround

Go to System Settings → Notifications and enable notifications for your terminal app (e.g. Ghostty). If your terminal doesn't appear in the list, it has never successfully delivered a notification — you'd need to use Terminal.app once to register "Script Editor", or install terminal-notifier.

Environment

  • macOS Sequoia (15.x)
  • Ghostty terminal
  • GSD notifications.enabled: true, all sub-toggles true
  • cmux.enabled: false (falls through to osascript path)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions