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:
-
/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).
-
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.
-
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
- Open Warp terminal
- Run
claude agents (or claude agents --dangerously-skip-permissions)
- Send a prompt to an agent
- Observe: no status notifications appear in Warp's Agent View panel
- 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:
- Claude Code side: Forward
terminalSequence JSON from hook subprocess stdout to the terminal in agents/daemon mode, just like in interactive mode.
- 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.
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 agentsmode, Claude Code spawns hook subprocesses as children of a daemon process that has no controlling terminal:The actual TTY belongs to the
claude agentsprocess, which is a sibling of the daemon — not an ancestor. This breaks both delivery paths inemit-terminal-sequence.sh:/dev/ttypath: Hook subprocesses have no controlling terminal, sottyreturns "not a tty" and writes to/dev/ttyfail silently (masked by2>/dev/null || true).terminalSequenceJSON 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.Parent process tree walk: Walking up the process tree from the hook subprocess only finds daemon processes (all TTY =
??), never reaching theclaude agentsprocess that owns the Warp pane's PTY.Environment
WARP_CLI_AGENT_PROTOCOL_VERSION=1is correctly setReproduction
claude agents(orclaude agents --dangerously-skip-permissions)claudein Warp works correctly — notifications appearWorkaround
I modified
emit-terminal-sequence.shto add a fallback in_warp_resolve_tty()that searches allclaudeprocesses for one with a real TTY when the parent-tree walk fails:With this change, the hook scripts write OSC 777 sequences directly to the TTY device (e.g.,
/dev/ttys000) of theclaude agentsprocess, and Warp correctly receives the notifications.However, this workaround has limitations:
claudesessions are running in different Warp panes.Suggested Fix
Ideally, one of these approaches would be adopted:
terminalSequenceJSON from hook subprocess stdout to the terminal in agents/daemon mode, just like in interactive mode._warp_resolve_tty()with session-awareness (usingWARP_TERMINAL_SESSION_UUIDor 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/ttybeing unavailable in hook subprocesses. This issue specifically identifies theclaude agents(daemon) mode as the trigger and proposes a concrete workaround.