Skip to content

Notifications silently fail under Claude Code: hook subprocess has no controlling TTY, OSC 777 write to /dev/tty is dropped #47

@Zmetser

Description

@Zmetser

Summary

When the plugin runs under Claude Code (CLAUDECODE=1), scripts/warp-notify.sh writes the OSC 777 escape sequence to /dev/tty, but Claude Code spawns hook subprocesses without a controlling TTY, so the write fails silently and no notification ever reaches Warp.

  • Plugin version: 2.0.0
  • Warp version: v0.2026.05.13.09.15.stable_01 (also reproduced on v0.2026.05.06.15.42.stable_04)
  • Claude Code: 2.1.142 on macOS (Darwin 25.3.0)

Repro

  1. Install the plugin in Claude Code, restart / /reload-plugins.
  2. Confirm WARP_CLI_AGENT_PROTOCOL_VERSION=1 and WARP_CLIENT_VERSION are set (they are).
  3. Confirm Warp's "Receive desktop notifications" + agent toggles are on.
  4. Run anything in Claude Code that triggers Stop, PermissionRequest, or idle Notification hooks.
  5. Expected: native Warp/macOS notification. Actual: nothing fires.

Root cause

Adding debug logging to warp-notify.sh shows every hook firing with:

/dev/tty: Device not configured
  /dev/tty exists but NOT writable
  tty: not a tty
  printf to /dev/tty FAILED

The hook subprocess Claude Code spawns has no controlling terminal, so > /dev/tty errors. The 2>/dev/null || true swallows the error, so the failure is invisible.

Workaround / suggested fix

Walk up the process tree to find the nearest ancestor with a real tty (the parent claude process keeps its tty, e.g. ttys003), and write the OSC sequence to that device directly. Patch that works locally:

find_parent_tty() {
    local pid=$PPID
    local tty
    while [ -n "$pid" ] && [ "$pid" != "1" ] && [ "$pid" != "0" ]; do
        tty=$(ps -o tty= -p "$pid" 2>/dev/null | tr -d ' ')
        if [ -n "$tty" ] && [ "$tty" != "??" ] && [ "$tty" != "-" ]; then
            echo "/dev/$tty"
            return 0
        fi
        pid=$(ps -o ppid= -p "$pid" 2>/dev/null | tr -d ' ')
    done
    return 1
}

TARGET_TTY=$(find_parent_tty)
if [ -n "$TARGET_TTY" ] && [ -w "$TARGET_TTY" ]; then
    printf '\033]777;notify;%s;%s\007' "$TITLE" "$BODY" > "$TARGET_TTY" 2>/dev/null || true
else
    printf '\033]777;notify;%s;%s\007' "$TITLE" "$BODY" > /dev/tty 2>/dev/null || true
fi

With this in place, Stop / PermissionRequest / idle notifications all fire correctly.

Minor

The resulting notifications show a generic folder icon rather than the Warp icon — not sure if that's a separate macOS notification-attribution issue or related to the OSC 777 path.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions