Skip to content

fix: an Orca started from an Orca terminal no longer leaks that pane's identity into chat agents - #28267

Draft
brennanb2025 wants to merge 5 commits into
mainfrom
brennanb2025/pane-identity-leak
Draft

brennanb2025 wants to merge 5 commits into
mainfrom
brennanb2025/pane-identity-leak

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Oct 13, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 6 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​336 $\color{#cf222e}{\Huge{\mathbf{−}}}$​219 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​117
Prod 7 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​92 $\color{#cf222e}{\Huge{\mathbf{−}}}$​30 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​62

ELI5

Every Orca terminal tells the programs inside it "you are pane X". If you start Orca itself from one of those terminals (a dev build, orca serve, or the app run from a pane), the new Orca wrongly believed it was pane X too, and passed that on to every chat agent it started. Those agents then reported their conversations as pane X's, and after a restart pane X "resumed" a chat's conversation as a terminal agent in the wrong workspace. Now Orca forgets the launching pane's identity when it starts, and no chat agent can carry a pane's identity whatever its environment says.

This PR stops new mix-ups. Panes that already saved a chat's conversation before this fix are handled by the stacked follow-up #28288 (a terminal never auto-resumes a conversation a chat owns).

What Changed

What the user saw (#26417). A background Claude chat worker started in worktree A came back after an Orca restart as a claude --resume <id> CLI tab in a floating terminal, with the home directory as its cwd, and did so again on every restart. Orca's own session record still placed the chat in worktree A.

Why it happened. The Orca that started the worker had been launched from a shell inside that floating pane, so its environment held the pane's ORCA_PANE_KEY, ORCA_TAB_ID, ORCA_WORKTREE_ID, launch token and hook route. Native-chat Claude and Codex children inherit the app's environment, and the shared step that names a chat's child only removed a pane's identity for the agents that asked (ACP and Pi). The launch refactor in #27204 deliberately kept that behaviour unchanged because it was a parity-only refactor. So the chat's hooks reported its conversation under the floating pane, the pane saved it in its restore record, and the pane's normal cold restore resumed it in the pane's own workspace.

After.

  • Starting Orca (desktop or the headless server) from an Orca terminal or chat no longer makes anything it spawns claim to be that terminal or chat.
  • Every native-chat agent's process (Claude, Codex, Grok, OpenCode, OMP, Pi) carries only its own chat's identity. A pane key, workspace hint, terminal handle or hook route can't reach it from the inherited environment, a settings env override, or a shell profile. A registered worker keeps its own terminal handle.
  • Terminal agents you run yourself, the orca CLI inside a pane, hooks and WSL panes are unchanged: each pane still gets its own identity explicitly when it is spawned.

Mechanism.

  1. Host boundary. Both host entry points (Electron main's startup and the headless server's start) already drop one piece of the launching terminal's identity, its runtime source stamp (fix(cli): check the runtime source before sending #27206). They now drop all of it from the host's own environment through one function: pane identity and its scrub-safe aliases, the pane's workspace hints and terminal handle, the pane's hook route, a chat session's caller identity, and a chat's scratch folder (under its current and pre-rename variable names). Orca never writes any of these into its own environment, so a value present at startup can only have come from whatever launched it. This covers every child the host spawns, including ones that are not chats (the Claude model-list probe, for one, starts Claude from the same environment). On Windows the process environment is case-insensitive, so one delete covers every spelling.
  2. The chat child's seal. The step every native-chat launch already passes through (sealStructuredSessionChild) now always strips the same pane identity and hook route from the composed environment, instead of only when an agent opts in. ACP's and Pi's own copies of that list are removed. One pane/hook key list is shared by both steps.
  3. Any casing on Windows. Windows reads a child's environment case-insensitively, so an orca_pane_key from a settings override or shell profile still names a pane. The seal now drops these keys in any casing on Windows. Only the seal does it, and only for the pane and hook keys: the final step that builds a provider child's environment also deletes a caller's own keys, which must keep their exact spelling, and the inherited host environment is already clean in every casing after startup.

Why

  • Fix where the identity comes from, not per agent. A per-agent opt-in list is what let Claude and Codex slip through. The host should never hold a pane's identity in the first place, and the one step that names every chat's child should never let one through. Both use the same list; neither is agent-specific.
  • No hook-ingest ancestry check. After this, nothing Orca spawns carries a foreign pane key. Proving process ancestry has no portable source (the hook's pid capture is Claude-only and POSIX-only; Windows, WSL and remote hosts report through relays; the terminal daemon reparents processes) and would reject legitimate status.

Supersedes #26426 by @petersindex, which found the same leak and stripped it in the chat child's environment. Thank you. That PR edits files #27204 has since replaced and covers only chat children; this one fixes the host boundary and makes the chat seal strip the identity for every agent.

Known limits. Background checks that list an agent's models or read its CLI version don't pass through the chat seal: they get the host's cleaned environment, but a pane key a shell profile or settings override adds after startup still reaches them. No effect on saved sessions was found; a follow-up can extend the same strip to them. A remote paired host is fixed once it updates (the headless server drops the identity at its own start). An older Orca keeps leaking until it is updated. No wire or stored format changes. Panes that saved a chat's conversation before this fix keep resuming it until #28288 lands.

Linked Issue

Part of #26417 (#28288, stacked on this PR, closes it).

Visual Proof

Pending: restart repro on a remote QA machine (Orca launched from an Orca pane, structured worker started, restart). Will attach.

Testing

  • inherited-launch-identity-env.test.ts, configure-process.test.ts: the host drops every key and keeps unrelated ones.

  • structured-session-child-env.test.ts: a chat or worker of an agent that asks for no deletions still loses a pane key, workspace hint and hook route supplied by its environment; a worker keeps its handle. On Windows, lower- and mixed-case pane keys, hook routes and handles from an overlay are gone after the final provider merge, while a deliberate lowercase override that is not identity (an OpenCode config dir) stays.

  • structured-agent-launch-parity.test.ts: the oracle of what every native-chat agent's child receives now models the host's environment after startup. The snapshot change is removals from the children's env (no Claude, Codex, ACP or Pi child carries the parent pane's keys, terminal handle, hook route or scratch folder; a worker keeps its own handle) plus the seal's now-uniform delete list.

  • Ablation: with the host drop and the seal strip disabled, the identity tests and the affected parity cases fail; with the seal's delete made case-sensitive, the Windows test fails.

  • Existing suites src/main/startup, src/main/acp, src/main/pi, src/main/claude, src/main/codex, parity: pass. pnpm tc:node and the repo's static checks pass.

  • I manually tested these changes locally

  • Automated tests added/updated, or explained why not below

Review

Agent skill upstream boundary

  • Not applicable, or this change copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

Cross-platform: plain environment deletes; Windows process.env is case-insensitive. SSH/remote: the headless server drops the identity at its own start. Folder workspaces: the folder hints are in the list. Performance: one pass over a short key list at startup and per chat launch. Backwards compatibility: no stored or wire format changes.

X: @BrennanKB5

…at pane's identity

When Orca itself is launched from a shell inside one of its terminal panes (a dev
build, `orca serve`, or the app run from a pane), its environment carries that
pane's identity: ORCA_PANE_KEY, ORCA_TAB_ID, ORCA_WORKTREE_ID, the launch token,
the pane's hook route, and so on. Every child the new Orca spawns inherited it.
Native-chat Claude and Codex sessions passed it straight through, so their hooks
reported the chat's conversation as that pane's agent, the pane saved it, and on
the next restart the pane resumed the chat's conversation as a terminal agent in
the pane's own workspace (#26417).

Both host entry points already drop one piece of the launching terminal's
identity (its runtime source stamp). They now drop all of it, from one list:
pane identity and its scrub-safe aliases, the pane's workspace hints and
terminal handle, the pane's hook route, a chat session's caller identity, and a
chat's visuals folder. Orca never sets any of these on itself, so nothing it
needs is lost; terminals still get their own identity explicitly at spawn.

The native-chat launch parity oracle now models the host's environment after
startup, and records that no agent's child carries the parent pane's keys.
The seal that names a native-chat child stripped the pane identity and hook
route only for agents that asked (ACP and Pi). Claude and Codex asked for
nothing, so a pane key reaching their environment from any source other than
the host's own (a settings env overlay or a shell profile) still reached the
child and its hooks.

The seal now always strips them, for every agent and from the composed
environment as well as the inherited one, keeping only the session's own
identity and a registered worker's handle. The key list is shared with the
host startup drop, and ACP and Pi no longer carry their own copies. The pane's
workspace hints and terminal handle join the list, so an ordinary chat no longer
carries a pane's handle either.
@brennanb2025
brennanb2025 force-pushed the brennanb2025/pane-identity-leak branch from a7a64f3 to 1766ed5 Compare October 13, 2026 09:50
@brennanb2025 brennanb2025 changed the title fix: an Orca started from an Orca terminal no longer leaks that pane's identity into chats, and panes never auto-resume a chat's conversation fix: an Orca started from an Orca terminal no longer leaks that pane's identity into chat agents Oct 13, 2026
Windows reads a child's environment case-insensitively, so an orca_pane_key from a
settings overlay or shell snapshot still named a pane after the seal and the final
provider merge deleted only the exact uppercase names. One case-aware delete now
backs the seal and both final merges.
The final provider merges delete a caller's own keys too, so deleting those in any
casing on Windows also removed deliberate lowercase overrides (an OpenCode config
dir, say). Only the seal needs it, for the pane and hook keys an overlay supplies;
the inherited host env is already clean in every casing after startup.
@brennanb2025

brennanb2025 commented Oct 14, 2026 •

Copy link
Copy Markdown
Contributor Author

Review loop status: converged at e4be9cc, synced with main once, ready for a human look (still draft, not merged).

What this PR does: an Orca started from inside an Orca terminal no longer passes that terminal's identity (its pane key, tab, workspace, launch token and hook route) to the native chat agents it starts, so a chat can't be mistaken for that terminal and saved as its conversation (the cause of #26417). The fix that repairs terminals that already saved a chat's conversation is #28288, stacked on this one.

Reviewed in three rounds plus an independent design challenge, which kept this design unchanged. A live check on a remote Mac confirmed a chat started from a terminal-launched Orca no longer carries the terminal's identity.

Synced with main at the end of review (e4be9cc). While this PR was in review, main renamed the chat's visuals-folder variable to a scratch-folder variable (#28389). The startup cleanup now removes both the new and the old name, and Pi keeps main's own scratch-folder removal while leaving the pane keys to the shared chat seal. Nothing on main made this fix redundant or contradicts it. A fresh independent review of the merge found no defects.

Verified at e4be9cc: node typecheck; the targeted tests (startup cleanup, chat seal, launch parity snapshot, Pi, ACP, headless server); lint and code-quality checks. With the startup cleanup disabled, its two tests fail.

CI: the only failing check is static analysis, on mobile/src/tasks/use-mobile-jira-connection-state.test.ts (broad object parameter type). That file came from main (#28616), which fails the same check; open PR #28689 fixes it. The test jobs sit behind that check, so they didn't run here; #28288 contains this whole PR and its test shards are reported there.

Not verified on real Windows/Linux machines (Windows casing is covered by tests).

Main renamed the chat visuals folder variable to ORCA_CHAT_SCRATCH_DIR: the
startup drop now removes it and the legacy name. Pi keeps main's scratch-folder
strip; the seal already covers the pane keys and handle Pi listed.

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant