From 80c63c88f0f0ff31331e3c84d04545a6fec14dca Mon Sep 17 00:00:00 2001 From: satoshi-takano-bloom Date: Sun, 7 Jun 2026 14:26:32 +0900 Subject: [PATCH] feat(desktop-notify): route OSC 9 notifications through Ghostty (#2114) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ghostty natively supports the OSC 9 desktop-notification escape (ESC ] 9 ; BEL), the same sequence already used for iTerm2. Previously only TERM_PROGRAM === 'iTerm.app' took the escape path, so Ghostty users fell through to the osascript path. That makes Script Editor the notification owner, and clicking the notification just launches Script Editor instead of focusing the terminal. Adding 'ghostty' to the OSC 9-capable check makes Ghostty the owner, so clicking the notification focuses the Ghostty window/tab where Claude Code is running. Verified on Ghostty (TERM_PROGRAM=ghostty). Co-authored-by: 高野智史 Co-authored-by: Claude Opus 4.8 (1M context) --- scripts/hooks/desktop-notify.js | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/scripts/hooks/desktop-notify.js b/scripts/hooks/desktop-notify.js index 1f6fdf3d..469648c7 100644 --- a/scripts/hooks/desktop-notify.js +++ b/scripts/hooks/desktop-notify.js @@ -154,20 +154,25 @@ function isUnderMultiplexer() { /** * Send a macOS notification. * - * On iTerm2 (and not inside tmux/screen), prefers the native escape sequence - * (ESC ] 9 ; BEL) written to the parent terminal's tty. This makes - * iTerm2 the notification owner, so clicking the notification focuses the - * exact iTerm2 tab where Claude Code is running. The default osascript path - * makes Script Editor the owner instead, which causes clicks to launch - * Script Editor. + * On terminals that support the OSC 9 notification sequence (iTerm2 and + * Ghostty), and when not inside tmux/screen, prefers the native escape + * sequence (ESC ] 9 ; BEL) written to the parent terminal's tty. + * This makes the terminal the notification owner, so clicking the + * notification focuses the exact tab/window where Claude Code is running. + * The default osascript path makes Script Editor the owner instead, which + * causes clicks to launch Script Editor. * - * Falls back to osascript when not running under iTerm2, when tty discovery - * fails, or when running inside a multiplexer that would swallow OSC 9. + * Falls back to osascript when not running under an OSC 9-capable terminal, + * when tty discovery fails, or when running inside a multiplexer that would + * swallow OSC 9. * AppleScript strings do not support backslash escapes, so we replace double * quotes with curly quotes and strip backslashes before embedding. */ function notifyMacOS(title, body) { - if (process.env.TERM_PROGRAM === 'iTerm.app' && !isUnderMultiplexer()) { + const osc9Capable = + process.env.TERM_PROGRAM === 'iTerm.app' || + process.env.TERM_PROGRAM === 'ghostty'; + if (osc9Capable && !isUnderMultiplexer()) { try { const tty = findTerminalTTY(); if (tty) {