Skip to content

Native chat: every agent launches through one composed launch path - #27204

Merged
brennanb2025 merged 5 commits into
mainfrom
brennanb2025/native-chat-one-launch-path
Oct 12, 2026
Merged

brennanb2025 merged 5 commits into
mainfrom
brennanb2025/native-chat-one-launch-path

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 48 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​3935 $\color{#cf222e}{\Huge{\mathbf{−}}}$​219 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​3716
Prod 43 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​873 $\color{#cf222e}{\Huge{\mathbf{−}}}$​602 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​271

ELI5

Orca's chat view runs six agents: Claude, Codex, OpenCode, Grok, OMP and Pi. Starting any of them takes the same handful of steps: pick the folder the chat runs in, build its environment, tell it which Orca chat it is, and strip settings it must not inherit.

Today each agent family has its own copy of those steps. When a newer agent's copy missed one, users saw bugs:

  • orchestration workers only worked for Claude and Codex;
  • floating-workspace chats of the newer agents started in the wrong folder.

This PR gives every agent one shared launch path, so a new agent can't skip a step. Nothing changes for users.

What Changed

User-facing: nothing. This is a strict-parity refactor. Every agent starts the same program, with the same arguments, folder and environment, in the same order as before.

Mechanism:

  • One composed launch per agent. Each agent's registration now builds its launcher through one shared composer (composeStructuredLaunch).
    • The composer admits the chat's stored record: it exists, this agent drives it, and it runs on this machine.
    • It hands the agent a basis to read from: the chat's launch folder (including the floating-workspace pin), the base environment plus the user's per-agent environment, visuals, and Command settings. The basis reads lazily, so each agent keeps its original order of steps.
    • The agent's own part maps that basis to its program, arguments and protocol. It can't read the record store or workspace paths, and it can't set the folder or the final child environment.
  • One seal. sealStructuredSessionChild is the only code that builds a chat process's identity environment. That environment is the chat's Orca session id, Orca's own CLI on PATH, and the spawn token.
    • Each launch must state which inherited keys to delete; that list is now required.
    • It replaces four separate copies and one spawn-token constant declared four times.
    • The spawn token is required on every launch. The one unsealed read, OpenCode's crash-recovery history read, has its own read-only path, so an unsealed environment can't reach a spawn.
  • Unused dependencies removed. Two dependencies no production code set are gone, and the per-agent environment overlays now come from one place.

Why

The bugs all came from one agent's launch skipping a step that the others repeat. Making each agent repeat the step correctly would leave the next agent to miss it again. Here the shared steps live in one composer and one seal that every registration has to go through.

Alternatives considered:

  • Resolving everything once in the shared attach path. That runs before Codex closes its previous process and outside each agent's abort and pre-spawn error handling, so it would not be parity.
  • Resolving everything eagerly up front. That would pin a floating chat's folder before a Command-setting error that today leaves it unpinned.

Parity evidence

  • A snapshot test lands first, in its own commit (test-only). It records the exact process each agent spawns, at the real spawn boundary: command, arguments, folder, environment (including PATH order) and environment deletions.
    • It covers all six agents, worktree and floating folders, fresh and resumed chats, and a worker.
    • It also covers non-default settings: a custom Command, permission bypass, a per-agent environment overlay, and a partial shell environment.
    • It passes on unmodified base, and the snapshot file is byte-identical after the refactor.
    • A second test fails if a newly registered agent has no snapshot case.
  • Differences the snapshot keeps exactly as on base. Each is a follow-up, not part of this PR:
    • Claude and Codex keep an inherited ORCA_PANE_KEY, hook environment, and tab, worktree and launch-token keys. OpenCode, Grok, OMP and Pi strip them.
    • Claude, Codex and the ACP agents keep an inherited ORCA_TERMINAL_HANDLE; Pi strips it unless the chat is a worker.
    • Pi has no inline visuals, never strips an inherited visuals folder, and always runs with full access.
    • Saved Arguments apply only to Claude and Codex.
    • Claude looks up its program on the startup PATH; the others use the launch environment's PATH.
    • Only ACP and Pi re-check the CLI version on every launch.

Linked Issue

N/A: maintainer refactor. It follows up the orchestration-workers fix (#26776) and the floating-folder fix (#26981).

Visual Proof

N/A: no user-visible change. Parity is proven by the spawn snapshot above.

Testing

  • Parity snapshot (commit 1, test-only), 43 tests. It records each agent's real spawned process at the @orca/process-host spawn boundary: command, arguments, folder, environment and deletions, plus each agent's permission settings.
    • Cases cover worktree and floating folders, fresh and resumed chats, a worker, and non-default settings: a custom Command, permission bypass, a per-agent environment overlay, and a partial shell environment.
    • The commit was pushed alone first, and CI passed on it against current main. That shows it describes main's behaviour.
    • The snapshot file is byte-identical through the refactor.
    • Reviewers checked that it catches regressions by breaking the code on purpose: dropping Pi's Command wiring, Codex's permission policy, the shell-environment policy, or ACP's delete list each fails cases.
  • Unit tests: targeted suites for ACP, Codex, Pi, Claude, native chat, provider process and runtime (12,325 tests) pass.
  • Real Claude CLI tests (ORCA_REAL_CLAUDE_CLI_TEST=1) pass 13/13.
  • Checks: pnpm tc:node and all 24 orca-ci-checks pass.
  • Manual: live QA on a hidden macOS build with real OpenCode is in progress; results will be posted in a comment.
  • I manually tested these changes locally
  • Automated tests added/updated

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

  • Overlaps: this touches the same Claude launch environment code as the SSH launch-environment draft (Fix CLI delivery and fresh launch environments for managed SSH agents #26464); whichever lands second rebases.
  • Cross-platform: the snapshot test skips Windows; Windows-only branches are unchanged by reading.
  • Remote/SSH: launches still run only on the host that owns the chat; nothing about remote dispatch changes.

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred)

…ity oracle

Captures the exact child each native-chat agent spawns (Claude, Codex, Grok,
OpenCode, OMP, Pi), worktree and floating, fresh and resumed, plus a registered
worker with visuals off, at the real spawn boundary (the process host's spawn)
and through production's own host dependency wiring. The inherited environment
holds every pane, hook, caller, terminal-handle and visuals key a parent Orca
pane could hand down, so the snapshot records today's per-agent differences as
they are. Each case also records the launch's permission posture, which for
Codex (thread policy) and the ACP agents (full access) acts only after spawn.

Each agent also runs fresh and resumed with every launch setting moved off its
default: a Command setting naming another binary, the permission posture
flipped, an environment overlay over inherited and shell keys, and only named
login-shell variables inherited (the fake login shell carries its own PATH and
a variable no case names).

A second test fails when a registered agent has no snapshot, since the oracle
lists its agents by hand.

Test only: the launch-path refactor that follows must leave both tests and the
snapshot file unchanged.
Every native-chat agent (Claude, Codex, Grok, OpenCode, OMP, Pi) now builds its
child through one composer instead of six hand-written resolvers. Each agent
contributes only its own part (command, args, agent env, keys it must strip);
the shared composer admits the session record, reads the launch directory,
base environment, launch overrides and visuals once each (memoized, in the
agent's original order), sets cwd, and seals the child.

The seal (sealStructuredSessionChild) is the one place that names a child:
session id, structured marker, worker terminal handle, the Orca CLI env, the
spawn token under one shared constant, and the per-launch list of inherited
keys to delete, minus any key the seal itself sets. Claude's delete list now
reaches its SDK spawn after buildClaudeChildProcessEnv's merge.

Behaviour is unchanged: the parity snapshot from the previous commit passes
byte for byte, including each agent's existing differences (Claude and Codex
keep inherited pane and hook env, Pi has no visuals and always full access,
the ACP history read stays unsealed). Admission refusals keep their reasons;
only log-only wording moved to the shared check.

Drops launch dependencies no production caller wires (resolveLaunchEnv,
resolveCodexOverrides) and folds the per-agent env overlay hooks into the
existing resolveAgentLaunchEnv.
Follow-ups from review of the composed launch:

- The spawn token is required on every resolveLaunch. The one read that
  starts no child, ACP's restart-recovery history read, now has its own
  path (composeStructuredLaunchRead, deps.readLaunch) that returns the
  agent's own env unsealed, as before. Agent parts receive the session
  identity only, never the token.
- structuredSessionChildIdentityEnv is gone; the seal is the only way to
  build a child's identity env, and tests go through it.
- A launch's environment is the runtime's resolveAgentEnvironment, so the
  base-plus-overlay rule exists once.
- Claude's connection test now covers a non-empty envToDelete applied
  after the inherited env is merged.

No change to any spawned child: the parity snapshot is unchanged.
@brennanb2025
brennanb2025 force-pushed the brennanb2025/native-chat-one-launch-path branch from efa20c3 to d5fd690 Compare October 10, 2026 09:55
The seal moved from structured-session-child-identity-env to
structured-session-child-env, but the PR job classifier still listed the old
path, so a change to only the seal would skip the live login-shell contract
job. The prefix now covers the seal, its test and its test support (which
the login-shell harness imports).
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Review status: ready for your review (draft, not merged)

The problem. Every native-chat agent (Claude, Codex, OpenCode, Grok, OMP, Pi) has to take the same steps to start:

  • pick the folder the chat runs in, keeping a floating-workspace chat in its pinned folder;
  • build its environment;
  • tell the process which Orca chat it is, so orca orchestration check from inside the chat finds itself;
  • strip terminal-pane settings it must not inherit.

Each agent family had its own copy of those steps, and two user-facing bugs came from a newer agent's copy missing one:

What this PR changes for users. Nothing. This is a strict-parity refactor: every agent now launches through one shared composer and one "seal". Agents can't set their own folder or final environment, so a new agent can't skip a step.

Fixed during review:

  • Round 1:
    • The parity snapshot covered only default settings. It now also covers a custom Command, permission bypass, a per-agent environment overlay and the shell-environment policy.
    • A test now fails if a newly registered agent has no snapshot.
    • The spawn token is required on every launch, and OpenCode's crash-recovery read has its own read-only path, so an unsealed environment can't reach a spawn.
    • The old identity-environment function is removed, so the seal is the only way to build that environment.
    • Added a test for Claude's new delete-list plumbing.
  • Round 2: made the shell-environment policy and each agent's permission posture visible in the snapshot. Found nothing else.
  • Rebuilt on current main. Main moved process spawning into @orca/process-host (Extract subprocess handling into @orca/process-host #26963), and the snapshot didn't build there.
    • The snapshot was rebuilt, pushed alone, and passed CI against main.
    • The refactor was replayed on top, and a reviewer confirmed it is identical to the reviewed version apart from three conflict resolutions.
  • Readiness: a CI path filter still named the old file, so a seal-only change would have skipped the live login-shell contract job. Fixed.

Deferred. Per-agent differences the snapshot keeps exactly as on main, each a separate follow-up:

  • Claude and Codex keep an inherited ORCA_PANE_KEY/hook environment.
  • Pi has no inline visuals and always runs with full access.
  • Saved Arguments apply only to Claude and Codex.
  • Claude's program lookup uses the startup PATH.
  • Only ACP and Pi re-check the CLI version.

Also deferred:

  • Making the launch env fields required, which would mean rewriting about 40 test fakes.
  • Admission error wording that only reaches logs, since it is unreachable behind an earlier check.

Verified:

  • Parity snapshot: 43 tests at the real spawn boundary. It passes on unmodified main (CI, plus two independent local re-runs) and is byte-identical through the refactor. Breaking the code on purpose, by dropping Pi's Command wiring, Codex's permission policy, the shell-environment policy or ACP's delete list, fails cases.
  • Local checks: targeted suites (12,325 tests), the real Claude CLI tests (13/13), pnpm tc:node, and all 24 orca-ci-checks.
  • CI at 422a743: green, 25/25.
  • Live QA on a hidden macOS build (332cf84) with real OpenCode:
    • A worktree chat runs in the repo.
    • A floating chat stays in its pinned folder after the setting changes and the app restarts.
    • The chat's process has its Orca session id, structured-session marker, spawn token and Orca CLI. An inherited ORCA_PANE_KEY/ORCA_TAB_ID is stripped, even though the app was started with them set.
    • orca orchestration check from inside the chat works.
    • No visible difference from main.

Not verified:

  • Claude, Codex, Grok and Pi live. They are covered by the snapshot, plus the real Claude CLI tests for Claude.
  • Windows, Linux and SSH live. The snapshot skips Windows.

Seen in QA, not from this PR:

  • In dev mode, the right sidebar and workbench show error cards, because the renderer imports @orca/process-host's CommonJS build, which Vite doesn't pre-bundle. This comes from main (Extract subprocess handling into @orca/process-host #26963).
  • Orca's chat-naming helper (opencode run) still inherits pane settings. It is outside the native-chat launch path.

@brennanb2025
brennanb2025 marked this pull request as ready for review October 12, 2026 01:12
@coderabbitai

coderabbitai Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

📝 Walkthrough

Walkthrough

The change adds shared structured-launch composition for ACP, Claude, Codex, and Pi RPC. Agent-specific launch parts provide launch data and environment settings to common runtime wiring. Child-environment sealing applies session identity, spawn tokens, and inherited-key deletion. Runtime environment resolution uses a shared per-agent overlay. Tests add launch-parity coverage and update launch, recovery, and environment checks.


Priority: ➖ Normal

Merge Risk: ⚪ Minimal · up to ead5e

This refactor is intended to preserve launch behavior, and no concrete regression was identified. The surviving comment is a small readability suggestion and does not block merge.

Pre-merge checks | Passed 4 | Failed 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 42 functions across 51 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check Passed The title clearly and concisely summarizes the main change: routing every native-chat agent through one composed launch path.
Description check Passed The description is comprehensive and follows the required template. It explains the user impact, mechanism, rationale, parity evidence, testing, cross-platform considerations, and checklist status. Th…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@brennanb2025

Copy link
Copy Markdown
Contributor Author

Live Electron QA: every native-chat agent still launches and behaves as before

A hidden macOS build of this PR at 422a743, with the real Claude, Codex, OpenCode, Grok, OMP and Pi CLIs, each on its own existing sign-in. A separate agent drove every click. All rows pass.

Check Result
New chat in a worktree for each of the 6 agents: replies, and runs in the worktree folder (pwd) Pass, all 6
Floating workspace chats (Claude, OpenCode) run in the floating folder Pass
Quit and relaunch, then ask the reopened chat what it said before (Claude, OpenCode) Pass: both remember. Claude relaunched with --resume
Orchestration workers (OpenCode, Claude) start as chats and report back worker_done Pass
Environment variable names each agent sees (values never printed) Matches this PR's description, see below

Environment seal. The app was started with a fake ORCA_PANE_KEY and ORCA_TAB_ID in its own environment, to check what each agent inherits.

  • Every agent got ORCA_AGENT_SESSION_ID, ORCA_AGENT_SESSION_SPAWN_TOKEN, ORCA_STRUCTURED_SESSION, ORCA_CLI_COMMAND and ORCA_USER_DATA_PATH.
  • Claude and Codex kept the inherited pane and tab keys; OpenCode, Grok, OMP and Pi had them removed.
  • Pi had no ORCA_CHAT_VISUALS_DIR.
  • Only workers carried ORCA_TERMINAL_HANDLE.

That is the same split as main.

Permission cards. Only OMP showed one (Allow once was clicked); the others showed none under the default Agent Permissions.

Agent config files. The agents' real config files were hashed before, during and after the run, with no changes.

Not covered: a side-by-side run of an origin/main build. Equality with main rests on the 43-case spawn snapshot in this PR.

Pi in a worktree:
Pi reply

OMP's permission card, with the new-tab menu that opened each chat:
OMP permission card

Floating panel: an OpenCode chat runs in the floating folder:
OpenCode floating

After quit and relaunch, Claude remembers its earlier reply (its environment list still has the pane and tab keys):
Claude resume

After quit and relaunch, OpenCode remembers its earlier reply (pane and tab keys removed):
OpenCode resume

An OpenCode orchestration worker reports worker_done:
OpenCode worker

…hat-one-launch-path

# Conflicts:
#	src/main/codex/codex-structured-launch-resolution.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/main/codex/codex-structured-launch-resolution.ts (1)

72-76: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Pass the already-resolved environment instead of calling basis.environment twice.

basis.environment is wrapped in once, so both calls return the same promise. This means the behavior is correct. However, the code awaits the environment twice through two paths. Resolve environment first and pass async () => environment to resolveCodexStructuredInvocation. That makes the single-read intent explicit.

♻️ Proposed refactor
-    const { command } = await resolveCodexStructuredInvocation({
-      ...(deps.resolveCommand ? { resolveCommand: deps.resolveCommand } : {}),
-      resolveEnvironment: basis.environment
-    })
-    const environment = await basis.environment()
+    const environment = await basis.environment()
+    const { command } = await resolveCodexStructuredInvocation({
+      ...(deps.resolveCommand ? { resolveCommand: deps.resolveCommand } : {}),
+      resolveEnvironment: async () => environment
+    })

ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 5c351d84-0c14-447a-909b-740121b0fb0a
📥 Commits

Reviewing files that changed from the base of the PR and between 422a743 and ead5e13.

⛔ Files ignored due to path filters (1)
  • src/main/runtime/__snapshots__/structured-agent-launch-parity.test.ts.snap is excluded by !**/*.snap
📒 Files selected for processing (3)
  • config/scripts/pr-code-change-scope.mjs
  • src/main/codex/codex-structured-launch-resolution.ts
  • src/main/runtime/structured-agent-launch-composition.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 4 remain after this review.

@brennanb2025
brennanb2025 merged commit d3182d4 into main Oct 12, 2026
69 of 72 checks passed
brennanb2025 added a commit that referenced this pull request Oct 12, 2026
Main's #27204 moved every native-chat agent onto one composed launch path.
This PR's launch-side changes are carried onto it:
- The composer's sources take the runtime's refreshAfterMissingProgram, and
  the launch basis exposes it; after a refresh its environment reads return
  the refreshed environment. Each agent's part (Claude, Codex, ACP, Pi)
  resolves its program through resolveAgainAfterRefreshOnMissingProgram with
  it, so a launch that finds no program waits for the one shared refresh.
- Claude resolves its command from the launch's own environment.
- The parity snapshot gains the three remote selectors the seal sets empty;
  nothing else in it changed.
brennanb2025 added a commit that referenced this pull request Oct 15, 2026
…27204)

* test(chat): snapshot every native-chat agent's spawned child as a parity oracle

Captures the exact child each native-chat agent spawns (Claude, Codex, Grok,
OpenCode, OMP, Pi), worktree and floating, fresh and resumed, plus a registered
worker with visuals off, at the real spawn boundary (the process host's spawn)
and through production's own host dependency wiring. The inherited environment
holds every pane, hook, caller, terminal-handle and visuals key a parent Orca
pane could hand down, so the snapshot records today's per-agent differences as
they are. Each case also records the launch's permission posture, which for
Codex (thread policy) and the ACP agents (full access) acts only after spawn.

Each agent also runs fresh and resumed with every launch setting moved off its
default: a Command setting naming another binary, the permission posture
flipped, an environment overlay over inherited and shell keys, and only named
login-shell variables inherited (the fake login shell carries its own PATH and
a variable no case names).

A second test fails when a registered agent has no snapshot, since the oracle
lists its agents by hand.

Test only: the launch-path refactor that follows must leave both tests and the
snapshot file unchanged.

* refactor(chat): one composed launch for every native-chat agent

Every native-chat agent (Claude, Codex, Grok, OpenCode, OMP, Pi) now builds its
child through one composer instead of six hand-written resolvers. Each agent
contributes only its own part (command, args, agent env, keys it must strip);
the shared composer admits the session record, reads the launch directory,
base environment, launch overrides and visuals once each (memoized, in the
agent's original order), sets cwd, and seals the child.

The seal (sealStructuredSessionChild) is the one place that names a child:
session id, structured marker, worker terminal handle, the Orca CLI env, the
spawn token under one shared constant, and the per-launch list of inherited
keys to delete, minus any key the seal itself sets. Claude's delete list now
reaches its SDK spawn after buildClaudeChildProcessEnv's merge.

Behaviour is unchanged: the parity snapshot from the previous commit passes
byte for byte, including each agent's existing differences (Claude and Codex
keep inherited pane and hook env, Pi has no visuals and always full access,
the ACP history read stays unsealed). Admission refusals keep their reasons;
only log-only wording moved to the shared check.

Drops launch dependencies no production caller wires (resolveLaunchEnv,
resolveCodexOverrides) and folds the per-agent env overlay hooks into the
existing resolveAgentLaunchEnv.

* refactor(chat): only the seal names a native-chat child

Follow-ups from review of the composed launch:

- The spawn token is required on every resolveLaunch. The one read that
  starts no child, ACP's restart-recovery history read, now has its own
  path (composeStructuredLaunchRead, deps.readLaunch) that returns the
  agent's own env unsealed, as before. Agent parts receive the session
  identity only, never the token.
- structuredSessionChildIdentityEnv is gone; the seal is the only way to
  build a child's identity env, and tests go through it.
- A launch's environment is the runtime's resolveAgentEnvironment, so the
  base-plus-overlay rule exists once.
- Claude's connection test now covers a non-empty envToDelete applied
  after the inherited env is merged.

No change to any spawned child: the parity snapshot is unchanged.

* ci: run the login-shell contract job for the renamed child-env seal

The seal moved from structured-session-child-identity-env to
structured-session-child-env, but the PR job classifier still listed the old
path, so a change to only the seal would skip the live login-shell contract
job. The prefix now covers the seal, its test and its test support (which
the login-shell harness imports).

(cherry picked from commit d3182d4)

Backport to 1.4.224: tests import spawnProcess/runProcess/ProcessSpec from src/shared/child-process instead of @orca/process-host (the parity test mocks run-process at the same spawn boundary).
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