Skip to content

[Bug]: Claude Code >=2.1.206 shared daemon breaks agent-status pane attribution — hooks inherit the daemon's stale ORCA_PANE_KEY #9236

Description

@BrianDai22

What happened

Since Claude Code ≥ 2.1.206, new claude TUI sessions are transparently hosted as workers under one long-lived shared background daemon (claude daemon run, spare-pool prewarm, bg-pty-host workers). Claude's hook commands (Orca's claude-hook.sh) now execute inside those daemon workers — which inherit the daemon's environment, not the pane's. ORCA_PANE_KEY / ORCA_TAB_ID / ORCA_AGENT_LAUNCH_TOKEN therefore point at whichever pane first spawned the daemon (possibly days ago, possibly a different workspace).

Result: every new Claude session posts its agent status under one stale pane key. New panes never get a status entry (no working/waiting/done in the sidebar, sessions look undetected), and the stale pane's entry is continuously clobbered by every other session's events, including sessions from other worktrees.

Environment

  • Orca 1.4.143 (macOS Tahoe 26.5 / Darwin 25.5.0, arm64)
  • Claude Code 2.1.210 (repros with any ≥ 2.1.206 daemon-takeover build; 2.1.205 is fine)
  • Local terminals, agentStatusHooksEnabled: true, hooks installed by Orca's managed controls

Repro

  1. Run any Claude session on CC ≥ 2.1.206 so the shared daemon starts (pane A).
  2. Create a new Orca terminal (pane B): orca terminal create --worktree active --command "claude" and send it a prompt.
  3. Inspect agent-hooks/last-status.json (or the sidebar): pane B has no entry; pane B's session id + prompt appear under pane A's entry.

Verified mechanics on a live system:

  • The pane-B TUI process has the correct ORCA_PANE_KEY in its env, but it is only a thin client; the session worker is a child of the shared daemon: claude daemon run → bg-pty-host … -- claude --session-id <uuid>. ps eww on the worker shows the daemon's ORCA_PANE_KEY (pane A) and stale ORCA_AGENT_HOOK_PORT.
  • Hooks fire in the worker, so the POST to /hook/claude carries pane A's key. (The stale ORCA_AGENT_HOOK_PORT is survivable because claude-hook.sh re-sources ORCA_AGENT_HOOK_ENDPOINT; the pane key has no such refresh path.)
  • Manually POSTing the same payload with pane B's key attributes correctly — listener plumbing is fine; only the env-derived pane identity is wrong.
  • The daemon roster (~/.claude/daemon/roster.json) confirms interactive pane launches are dispatched from the spare pool ("source": "spare") with --session-id <uuid> injected by the daemon, and its dispatch env forwards only an allowlist (ANTHROPIC_*, AWS_*, CLAUDE_CONFIG_DIR, …) — per-pane ORCA_* vars don't survive into workers.

Why Orca can't see this today

AgentHookServer trusts the posted paneKey (after legacyPaneKeyAliases normalization). For daemon-hosted sessions there is no event that ever carries the true pane identity, so no amount of listener-side heuristics can recover it — the binding has to be established at spawn time, where Orca still knows the pane.

Proposed fix (happy to send a PR)

Mirror the existing legacyPaneKeyAliases pattern with a provider-session alias:

  1. Spawn-side pinning — in the local PTY spawn paths (src/main/ipc/pty.ts, the two isClaudeLaunchCommand sites): when spawning a Claude launch with a validated pane identity, mint a session UUID, append --session-id <uuid> (skip when the command already has --session-id / --resume / --continue), and record sessionId → paneKey.
  2. Listener-side override — in AgentHookServer (src/main/agent-hooks/server.ts), alongside normalizeHookBodyPaneKeyAlias: if the hook payload's session_id matches a recorded binding and the posted pane key differs, rewrite paneKey/tabId to the recorded pane. Unknown session ids pass through unchanged (user-typed claude in a plain shell keeps today's behavior).

This also future-proofs attribution against any other agent CLI that adopts a shared-daemon architecture, since the same mechanism generalizes to any provider whose hook payload carries a session id.

Two smaller adjacent gaps noticed while debugging (can split out):

  • claudeLivePtySessionIds never tracks sessions launched via agentCmdOverrides wrappers whose binary isn't literally named claude (e.g. claude-bedrock), because isClaudeLaunchCommand matches the bare name only.
  • Daemon-hosted sessions also defeat markClaudePtySpawned liveness assumptions (the TUI client PTY is a thin attach; the session outlives it in the daemon).

Workaround for affected users

CLAUDE_CODE_DISABLE_AGENT_VIEW=1 claude (disables Claude's daemon/agents view for that launch; hooks then run in-process with the pane's own env and attribution is correct).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions