Skip to content

Notifications not delivered in claude agents mode — hook subprocesses have no TTY and terminalSequence is not forwarded #62

@zxyblzcat

Description

@zxyblzcat

Problem

When running Claude Code via claude agents (the multi-agent/headless mode), the Warp plugin notifications are silently dropped. The Warp Agent View panel does not show any status updates (running, idle, waiting for input, etc.).

Root Cause

In claude agents mode, Claude Code spawns hook subprocesses as children of a daemon process that has no controlling terminal:

hook → zsh → claude (bg-spare) → claude (pty-host) → claude (daemon) → launchd
         (all TTY = ??)

The actual TTY belongs to the claude agents process, which is a sibling of the daemon — not an ancestor. This breaks both delivery paths in emit-terminal-sequence.sh:

  1. /dev/tty path: Hook subprocesses have no controlling terminal, so tty returns "not a tty" and writes to /dev/tty fail silently (masked by 2>/dev/null || true).

  2. terminalSequence JSON path (CC >= 2.1.141): The hook scripts correctly output {"terminalSequence": "..."} to stdout, but in agents/daemon mode, this output is not forwarded to the terminal by the Claude Code daemon. The JSON is simply discarded.

  3. Parent process tree walk: Walking up the process tree from the hook subprocess only finds daemon processes (all TTY = ??), never reaching the claude agents process that owns the Warp pane's PTY.

Environment

  • Claude Code: 2.1.158
  • Warp: v0.2026.05.27.15.44.stable_01
  • Plugin: warp@claude-code-warp v2.1.0
  • OS: macOS (Darwin 24.6.0)
  • WARP_CLI_AGENT_PROTOCOL_VERSION=1 is correctly set

Reproduction

  1. Open Warp terminal
  2. Run claude agents (or claude agents --dangerously-skip-permissions)
  3. Send a prompt to an agent
  4. Observe: no status notifications appear in Warp's Agent View panel
  5. Compare: running plain claude in Warp works correctly — notifications appear

Workaround

I modified emit-terminal-sequence.sh to add a fallback in _warp_resolve_tty() that searches all claude processes for one with a real TTY when the parent-tree walk fails:

# 3. In `claude agents` mode, the TTY belongs to a sibling process.
#    Find any claude process that has a real TTY.
tty_dev=$(ps -eo tty,comm 2>/dev/null \
    | grep -E 'claude' \
    | grep -vE '^\?\?' \
    | head -1 \
    | awk '{print $1}')
if [ -n "$tty_dev" ]; then
    echo "/dev/$tty_dev"
    return 0
fi

With this change, the hook scripts write OSC 777 sequences directly to the TTY device (e.g., /dev/ttys000) of the claude agents process, and Warp correctly receives the notifications.

However, this workaround has limitations:

  • It picks the first claude process with a TTY, which may be wrong if multiple claude sessions are running in different Warp panes.
  • Plugin updates will overwrite the modification.

Suggested Fix

Ideally, one of these approaches would be adopted:

  1. Claude Code side: Forward terminalSequence JSON from hook subprocess stdout to the terminal in agents/daemon mode, just like in interactive mode.
  2. Plugin side: Add the cross-process TTY discovery fallback to _warp_resolve_tty() with session-awareness (using WARP_TERMINAL_SESSION_UUID or similar to disambiguate when multiple sessions exist).

Related Issues

This is the same underlying cause as #43, #47, #49, #53, #54, #55, #58 — all report /dev/tty being unavailable in hook subprocesses. This issue specifically identifies the claude agents (daemon) mode as the trigger and proposes a concrete workaround.

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