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
- Run any Claude session on CC ≥ 2.1.206 so the shared daemon starts (pane A).
- Create a new Orca terminal (pane B):
orca terminal create --worktree active --command "claude" and send it a prompt.
- 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:
- 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.
- 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).
What happened
Since Claude Code ≥ 2.1.206, new
claudeTUI sessions are transparently hosted as workers under one long-lived shared background daemon (claude daemon run, spare-pool prewarm,bg-pty-hostworkers). Claude's hook commands (Orca'sclaude-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_TOKENtherefore 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
agentStatusHooksEnabled: true, hooks installed by Orca's managed controlsRepro
orca terminal create --worktree active --command "claude"and send it a prompt.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:
ORCA_PANE_KEYin 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 ewwon the worker shows the daemon'sORCA_PANE_KEY(pane A) and staleORCA_AGENT_HOOK_PORT./hook/claudecarries pane A's key. (The staleORCA_AGENT_HOOK_PORTis survivable becauseclaude-hook.shre-sourcesORCA_AGENT_HOOK_ENDPOINT; the pane key has no such refresh path.)~/.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-paneORCA_*vars don't survive into workers.Why Orca can't see this today
AgentHookServertrusts the postedpaneKey(afterlegacyPaneKeyAliasesnormalization). 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
legacyPaneKeyAliasespattern with a provider-session alias:src/main/ipc/pty.ts, the twoisClaudeLaunchCommandsites): 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 recordsessionId → paneKey.AgentHookServer(src/main/agent-hooks/server.ts), alongsidenormalizeHookBodyPaneKeyAlias: if the hook payload'ssession_idmatches a recorded binding and the posted pane key differs, rewritepaneKey/tabIdto the recorded pane. Unknown session ids pass through unchanged (user-typedclaudein 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):
claudeLivePtySessionIdsnever tracks sessions launched viaagentCmdOverrideswrappers whose binary isn't literally namedclaude(e.g.claude-bedrock), becauseisClaudeLaunchCommandmatches the bare name only.markClaudePtySpawnedliveness 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).