Skip to content

Commit 9658207

Browse files
committed
fix: resolve controlling TTY for hook processes without /dev/tty
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.
1 parent b8ad3cc commit 9658207

3 files changed

Lines changed: 46 additions & 4 deletions

File tree

plugins/warp/scripts/legacy/warp-notify.sh

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,13 @@
22
# Warp notification utility using OSC escape sequences
33
# Usage: warp-notify.sh <title> <body>
44

5+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
6+
source "$SCRIPT_DIR/../resolve-tty.sh"
7+
58
TITLE="${1:-Notification}"
69
BODY="${2:-}"
710

811
# OSC 777 format: \033]777;notify;<title>;<body>\007
9-
# Write directly to /dev/tty to ensure it reaches the terminal
10-
printf '\033]777;notify;%s;%s\007' "$TITLE" "$BODY" > /dev/tty 2>/dev/null || true
12+
# Hook processes have no controlling terminal, so resolve the real tty of an
13+
# ancestor process rather than relying on /dev/tty (which fails in that case).
14+
printf '\033]777;notify;%s;%s\007' "$TITLE" "$BODY" > "$(resolve_tty_device)" 2>/dev/null || true
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
#!/bin/bash
2+
# Resolve the terminal device to write OSC escape sequences to.
3+
#
4+
# Claude Code spawns hook processes WITHOUT a controlling terminal, so the
5+
# usual `/dev/tty` is unavailable ("Device not configured" / ENXIO) and any
6+
# notification written there is silently dropped. Walk up the process tree
7+
# to find an ancestor (the `claude` process or its parent shell) that still
8+
# has a controlling tty, and return that device node instead.
9+
#
10+
# Falls back to `/dev/tty` when no ancestor with a tty is found, so callers
11+
# behave exactly as before in environments where `/dev/tty` already works.
12+
#
13+
# Usage:
14+
# source "$SCRIPT_DIR/resolve-tty.sh"
15+
# printf '...' > "$(resolve_tty_device)"
16+
17+
resolve_tty_device() {
18+
local pid=$PPID depth=0 tty_name
19+
while [ -n "$pid" ] && [ "$pid" -gt 1 ] && [ "$depth" -lt 25 ]; do
20+
# `ps -o tty=` prints e.g. `ttys003` (macOS) or `pts/3` (Linux),
21+
# and `?` / `??` for a process with no controlling terminal.
22+
tty_name=$(ps -o tty= -p "$pid" 2>/dev/null | tr -d '[:space:]')
23+
case "$tty_name" in
24+
'' | '?' | '??')
25+
: # this ancestor has no controlling tty — keep walking up
26+
;;
27+
*)
28+
printf '/dev/%s\n' "$tty_name"
29+
return 0
30+
;;
31+
esac
32+
pid=$(ps -o ppid= -p "$pid" 2>/dev/null | tr -d '[:space:]')
33+
depth=$((depth + 1))
34+
done
35+
printf '/dev/tty\n'
36+
}

plugins/warp/scripts/warp-notify.sh

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
99
source "$SCRIPT_DIR/should-use-structured.sh"
10+
source "$SCRIPT_DIR/resolve-tty.sh"
1011

1112
# Only emit notifications when we've confirmed the Warp build can render them.
1213
if ! should_use_structured; then
@@ -17,5 +18,6 @@ TITLE="${1:-Notification}"
1718
BODY="${2:-}"
1819

1920
# OSC 777 format: \033]777;notify;<title>;<body>\007
20-
# Write directly to /dev/tty to ensure it reaches the terminal
21-
printf '\033]777;notify;%s;%s\007' "$TITLE" "$BODY" > /dev/tty 2>/dev/null || true
21+
# Hook processes have no controlling terminal, so resolve the real tty of an
22+
# ancestor process rather than relying on /dev/tty (which fails in that case).
23+
printf '\033]777;notify;%s;%s\007' "$TITLE" "$BODY" > "$(resolve_tty_device)" 2>/dev/null || true

0 commit comments

Comments
 (0)