From 965820798c2316a73cbdb3e2adbe9b0aa8e9687c Mon Sep 17 00:00:00 2001 From: Rladmsrl Date: Mon, 18 May 2026 17:25:26 +0800 Subject: [PATCH] fix: resolve controlling TTY for hook processes without /dev/tty MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Claude Code spawns hook processes without a controlling terminal, so `printf ... > /dev/tty` fails with "Device not configured" (ENXIO) and the OSC notification is silently dropped — Warp never receives the cli-agent event (no notifications, status, or footer ever appear). Add a shared resolve-tty.sh helper that walks up the process tree to an ancestor (the claude process or its parent shell) that still has a controlling tty, and writes the OSC sequence to that device node. Falls back to /dev/tty when no ancestor tty is found, so behavior is unchanged in environments where /dev/tty already works. Applies to both the structured (warp-notify.sh) and legacy (legacy/warp-notify.sh) notification paths. --- plugins/warp/scripts/legacy/warp-notify.sh | 8 +++-- plugins/warp/scripts/resolve-tty.sh | 36 ++++++++++++++++++++++ plugins/warp/scripts/warp-notify.sh | 6 ++-- 3 files changed, 46 insertions(+), 4 deletions(-) create mode 100644 plugins/warp/scripts/resolve-tty.sh diff --git a/plugins/warp/scripts/legacy/warp-notify.sh b/plugins/warp/scripts/legacy/warp-notify.sh index 6ca0588..fc67ef7 100755 --- a/plugins/warp/scripts/legacy/warp-notify.sh +++ b/plugins/warp/scripts/legacy/warp-notify.sh @@ -2,9 +2,13 @@ # Warp notification utility using OSC escape sequences # Usage: warp-notify.sh <body> +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +source "$SCRIPT_DIR/../resolve-tty.sh" + TITLE="${1:-Notification}" BODY="${2:-}" # OSC 777 format: \033]777;notify;<title>;<body>\007 -# Write directly to /dev/tty to ensure it reaches the terminal -printf '\033]777;notify;%s;%s\007' "$TITLE" "$BODY" > /dev/tty 2>/dev/null || true +# Hook processes have no controlling terminal, so resolve the real tty of an +# ancestor process rather than relying on /dev/tty (which fails in that case). +printf '\033]777;notify;%s;%s\007' "$TITLE" "$BODY" > "$(resolve_tty_device)" 2>/dev/null || true diff --git a/plugins/warp/scripts/resolve-tty.sh b/plugins/warp/scripts/resolve-tty.sh new file mode 100644 index 0000000..1613f8b --- /dev/null +++ b/plugins/warp/scripts/resolve-tty.sh @@ -0,0 +1,36 @@ +#!/bin/bash +# Resolve the terminal device to write OSC escape sequences to. +# +# Claude Code spawns hook processes WITHOUT a controlling terminal, so the +# usual `/dev/tty` is unavailable ("Device not configured" / ENXIO) and any +# notification written there is silently dropped. Walk up the process tree +# to find an ancestor (the `claude` process or its parent shell) that still +# has a controlling tty, and return that device node instead. +# +# Falls back to `/dev/tty` when no ancestor with a tty is found, so callers +# behave exactly as before in environments where `/dev/tty` already works. +# +# Usage: +# source "$SCRIPT_DIR/resolve-tty.sh" +# printf '...' > "$(resolve_tty_device)" + +resolve_tty_device() { + local pid=$PPID depth=0 tty_name + while [ -n "$pid" ] && [ "$pid" -gt 1 ] && [ "$depth" -lt 25 ]; do + # `ps -o tty=` prints e.g. `ttys003` (macOS) or `pts/3` (Linux), + # and `?` / `??` for a process with no controlling terminal. + tty_name=$(ps -o tty= -p "$pid" 2>/dev/null | tr -d '[:space:]') + case "$tty_name" in + '' | '?' | '??') + : # this ancestor has no controlling tty — keep walking up + ;; + *) + printf '/dev/%s\n' "$tty_name" + return 0 + ;; + esac + pid=$(ps -o ppid= -p "$pid" 2>/dev/null | tr -d '[:space:]') + depth=$((depth + 1)) + done + printf '/dev/tty\n' +} diff --git a/plugins/warp/scripts/warp-notify.sh b/plugins/warp/scripts/warp-notify.sh index 523f873..b9a7af1 100755 --- a/plugins/warp/scripts/warp-notify.sh +++ b/plugins/warp/scripts/warp-notify.sh @@ -7,6 +7,7 @@ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" source "$SCRIPT_DIR/should-use-structured.sh" +source "$SCRIPT_DIR/resolve-tty.sh" # Only emit notifications when we've confirmed the Warp build can render them. if ! should_use_structured; then @@ -17,5 +18,6 @@ TITLE="${1:-Notification}" BODY="${2:-}" # OSC 777 format: \033]777;notify;<title>;<body>\007 -# Write directly to /dev/tty to ensure it reaches the terminal -printf '\033]777;notify;%s;%s\007' "$TITLE" "$BODY" > /dev/tty 2>/dev/null || true +# Hook processes have no controlling terminal, so resolve the real tty of an +# ancestor process rather than relying on /dev/tty (which fails in that case). +printf '\033]777;notify;%s;%s\007' "$TITLE" "$BODY" > "$(resolve_tty_device)" 2>/dev/null || true