Skip to content

refactor(pty): one spawn preflight step for both launch lanes - #29101

Merged
brennanb2025 merged 2 commits into
mainfrom
brennanb2025/conv-c5b-shared-spawn-preflight
Oct 15, 2026
Merged

brennanb2025 merged 2 commits into
mainfrom
brennanb2025/conv-c5b-shared-spawn-preflight

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 3 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​163 $\color{#cf222e}{\Huge{\mathbf{−}}}$​11 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​152
Prod 8 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​517 $\color{#cf222e}{\Huge{\mathbf{−}}}$​161 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​356

ELI5

Orca can start a terminal two ways: the window asks for it directly, or Orca's host starts it (for the CLI, agent launches and paired devices). Each way kept its own copy of the very first step: "which folder does this terminal start in, and which shell runs it?" Two copies drift apart, and that drift is why an earlier attempt to move the "+" button onto the host's path had to be reverted (#27173). This PR makes that first step one shared function. Nothing changes for users yet: the two places where the copies still disagree are passed in as explicit, temporary inputs, each with the follow-up that removes it.

What Changed

Before (on main):

  • The window path (pty:spawn, ipc/spawn-preflight.ts) resolved the start folder with a "fall back to the workspace root, and tell the user" rule when the folder is gone (with a WSL folder probe), and picked the shell from the project runtime the renderer sent.
  • The host path (runtime/spawn-preflight.ts) resolved the start folder plainly (a deleted folder reaches the provider, which refuses it with "Working directory … does not exist") and picked the shell from the store (repo → project only).
  • The window also computed the same shell options twice, and recomputed the Codex account target a second time later.

After:

  • resolvePtySpawnStartupTarget (src/main/ipc/pty/spawn-startup-target.ts) returns the start folder (plus the fallback notice and the WSL-probed folder when they apply), the shell/runtime options, the expected WSL distro and the Codex account target. Both lanes call it.
  • Two TEMPORARY inputs carry the only remaining differences, so behaviour is identical to main:
Input Window Host Removed by
missingCwdFallback on: workspace-root fallback + notice, WSL cwd probe off: the provider refuses a missing folder row 13a (host starts get the fallback and notice; it also deletes the matching cwdFallback strip for pane agent launches in ipc/spawn-agent-launch.ts)
projectRuntimeSource sent: the runtime the renderer sends (reads a \\wsl$ path as WSL; a folder borrows its repo's) store: repo → project only row 13b (host resolution matches the renderer's)
  • The OpenSSH login shell on a managed SSH Windows host ([Bug]: Windows SSH project terminal ignores the configured PowerShell 7 login shell and starts PowerShell 5.1 #9327) is not a third input: the shared resolution reads it for both lanes. Only an SSH-launched orcad sets the env that turns it on, and orcad installs no IPC surface (setPtyHostBindings runs only in the Electron desktop preflight), so pty:spawn never runs there. On the desktop window lane it is a no-op.
  • Both lanes now resolve after fresh-spawn recovery and session-id mint, because the expected WSL distro reads the session id and the window lane may re-mint it during recovery. Admission, stable-pane adoption, the hidden-at-spawn mark, recovery and the mint stay exactly where they were in each lane.

Users see: no change.

Why

This is row 3 of the launch-convergence plan: every later step that moves a producer onto one spawn path (rows 14–19) needs the first spawn step to be one function, and rows 13a/13b need one place to delete their temporary input from. Passing the differences as named inputs keeps this PR a pure merge of duplicated code; fixing either difference here would change what users see and belongs in its own PR.

Alternatives considered:

  • Keep the call where each lane resolved its cwd before (ahead of fresh-spawn recovery): the expected WSL distro would then read a session id the window lane can still replace, so it would need a second shared call after recovery. One call after recovery is simpler; the resolution is pure apart from the window's WSL probe.
  • Make the OpenSSH login shell a parameter: it would be a third temporary input for a state the window lane can never reach.

Linked Issue

Part of the launch-convergence plan (row 3); no issue.

Visual Proof

N/A: no user-visible change. The pins below show both lanes resolve exactly as on main.

Testing

Commit 1 pins main's behaviour before the refactor (passes on main):

  • Row-8 parity cases (agents started with their workspace), carried over from the earlier row-8 work: 80/80 with the existing parity suites.
  • New pins in pty-launch-parity.test.ts: a deleted folder (window falls back and says so; host passes the folder on); a Windows plain terminal in a folder containing a WSL-project repo and in a \\wsl$ repo with no saved runtime (window uses the sent runtime; host uses the settings shell); a managed SSH Windows host (login shell).
  • New pin in pty-agent-pane-launch.test.ts: a pane's own agent launch drops the root fallback (TEMPORARY parity in spawn-agent-launch.ts).

Commit 2 keeps every existing suite unchanged and green: pty-launch-parity (rows 1–8, 10), pty-launch-race-parity, pty-spawn-cwd-fallback, pty-wsl-cwd-validation, pty-daemon-spawn-wsl-runtime, runtime/spawn-preflight-requested-shell, pty-controller-spawn-admission, both hidden-at-spawn-mark suites, pty-pane-materialization-race, plus 19 neighbouring spawn/env suites. One pin flips on purpose: the managed-SSH case now gives the window lane the login shell too (unreachable in production, see above). Each TEMPORARY input has a pin that fails when the input is ignored (checked by forcing each input off).

  • Automated tests added/updated
  • Manual: none; no user-visible change. Lint and typecheck: CI.

Platforms: logic covered on macOS, Linux and Windows paths through the platform-switching parity suites; SSH: the window lane never runs on a managed SSH host, and the host lane's login-shell behaviour is pinned.

Size

Production net +54 (+208 / −154), over the plan's +30 estimate; accepted by the lane owner with this breakdown. The generated table at the top counts the four *.test-fixture.ts files (+316) as production; they are test code.

Area Lines What it is Deleted later by
New spawn-startup-target.ts +165 fallback / WSL-probe step moved from the window (≈75), the shared resolution (≈35, replaces ≈30 window + ≈20 host lines), seam types and imports (≈55) row 13a removes the missingCwdFallback input (the fallback step stays, on for both lanes); row 13b removes projectRuntimeSource (the sent branch)
Window ipc/spawn-preflight.ts −96 net its copy of the cwd fallback, WSL probe, shell/runtime, WSL distro and Codex target —
Host runtime/spawn-preflight.ts −9 net its runtime step was only two calls; the session-id mint lines moved above the call (the WSL distro reads the session id) —
ipc/spawn-env-codex.ts −6 identical second computation of the Codex account target —

Tests: row-8 characterization fixtures +329 (carried over, plan-mandated); new pins ≈+140.

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

Collisions: #28136 (parked) rebases after this and is the natural partner of row 13b; #28180 and #23228 touch the same files textually.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • N/A with reason for visual proof
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (CI will cover)

Row 3 (one spawn preflight for both launch lanes) first pins main's behaviour:

- Row 8, agents started with their workspace (from the earlier row-8 work): a work item's
  "start agent" and the folder-workspace composer through the real producer and pane and
  pty:spawn: draft and note on argv, a long multi-line note, PowerShell quoting under cmd.exe,
  project WSL, terminal shell WSL, and a prompt-less Codex start with no shell wait.
- A deleted folder: the window starts in the workspace root and says so; the host passes the
  folder on and the provider refuses it.
- A Windows plain terminal in a folder containing a WSL-project repo, and in a \\wsl$ repo with
  no saved runtime: the window uses the runtime the renderer sends; the host reads only the
  repo's project.
- A managed SSH Windows host: only the host lane opens the OpenSSH login shell.
- A pane's own agent launch drops the root fallback (TEMPORARY parity).
The window's pty:spawn and the host's runtime controller each kept their own copy of the
first spawn step: resolve the folder a new terminal starts in and the shell/runtime its PTY
uses. resolvePtySpawnStartupTarget now does both for both lanes. Each lane passes the two
inputs it still differs on, so nothing changes for users:

- missingCwdFallback (TEMPORARY, row 13a): only window starts fall back to the workspace root,
  with a notice, when the requested folder is gone (including the WSL cwd probe).
- projectRuntimeSource (TEMPORARY, row 13b): the window uses the runtime its renderer sent;
  the host reads the repo's project from the store.

The OpenSSH login shell (#9327) now resolves for both lanes; only an SSH-launched orcad turns
it on, and that process has no window, so the window lane never meets it.

Both lanes resolve after fresh-spawn recovery and session-id mint (the WSL distro reads the
session id). Admission, adoption, hidden mark, recovery and mint stay where they were.
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Review loop status (head cbe112e)

The problem this is part of. Orca starts a terminal two ways. "+" tabs, split panes and agent launches from the window go through one path. orca terminal create, host-side agent launches and the phone go through another. Each path kept its own copy of the decision "which folder does this terminal start in, and which shell or WSL distro runs it". When "+" was moved onto the host path earlier, five differences between those copies showed up as user-visible regressions and the move was reverted. This PR is the first step in removing the copies: both paths now ask one function for the starting folder, shell/runtime, WSL distro and Codex account target.

User-facing change: none intended, none found. Every entry point gives the same result as on main for the same input:

  • "+" tab, split, a tab whose folder was deleted (falls back to the workspace root with the same notice), WSL tab with a missing folder, pane agent launch, restored terminals.
  • orca terminal create (plain and --shell), host agent launch, phone terminal, a deleted folder on the host path (same refusal and error text as on main).

The two places where the paths still differ (the missing-folder fallback exists only on the window path, and the window sends its shell/runtime while the host reads it from saved state) are kept on purpose as two named inputs. Each one is listed in the body with the later step that removes it. The one deliberate change is that the window path can now choose the OpenSSH login shell on a managed Windows SSH host. A real app never reaches that case: the managed SSH server runs under Node and never installs the window's terminal-start handler.

Review. Round 1: three adversarial reviewers plus an architecture challenger. Then a readiness pass on the final diff. No P0, P1 or P2 issues.

  • Fixed (PR body only, no code): the TEMPORARY table now names both places that carry the "agent launch never falls back to the root folder" difference. The Size section now explains that the generated size table counts the four *.test-fixture.ts files (+316 lines) as production.
  • Deferred (P3, none user-visible):
    • With ORCA_PTY_SPAWN_TIMING set, recovery time is now logged under "preflight" instead of "auth".
    • On Windows, the "project runtime needs repair" error now comes after one terminal-daemon recovery attempt instead of before it. The error text is unchanged.
    • Two pins are unchanged from main: the order of folder resolution after recovery, and the session-id → WSL-distro input. They belong to the later step that unifies runtime resolution.
    • The renderer row-8 test pins could have been a test-only PR of their own.

Verified.

  • Named-file test suites for every touched mechanism, run locally: 1024/1030 passed (6 skipped), and 194/194 in the readiness pass.
  • CI's full PR Checks run passed on this head.
  • Real app QA, main vs this PR. Both builds ran on one Mac, one after the other and hidden, each with a throwaway profile and home. A stub stood in for the agent CLI. Every row gave the same result on both builds (details below).

QA: main (ef563bccf4b, this PR's base) vs this PR (cbe112e0296), same inputs. <repo> is a git project, <folder> a non-git folder project.

Entry point Main This PR
"+" → New Terminal in <repo> starts in <repo>, login zsh same
Split that pane right <repo> same
New terminal in <folder> <folder> same
New tab → Claude (window) terminal tab, agent starts in <repo>, zsh same
Split a pane whose shell sits in a deleted folder new pane opens at <repo>, no notice (the notice is for a saved start folder that vanished, which the tests cover) same
Switch workspaces, close a running tab "Stop running command?" dialog, other terminals keep their output same
Quit and relaunch every tab and terminal restored in the same folders same
orca terminal create in <repo> / <folder> starts in that folder, login zsh same
orca terminal create --shell bash.exe on macOS refused: "names a Windows shell, and this execution host is darwin…" same text
orca agent launch --agent claude terminal tab, agent starts in <repo> same

Split into a deleted folder (terminal text blurred, it shows local paths), main then this PR:

main: split into a deleted folder
PR: split into a deleted folder

Not verified.

@brennanb2025
brennanb2025 marked this pull request as ready for review October 15, 2026 22:07
@coderabbitai

coderabbitai Bot commented Oct 15, 2026

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: e1692855-2bd6-4ec5-89a4-ad263ec2fddc
📥 Commits

Reviewing files that changed from the base of the PR and between 6ade7a5 and cbe112e.

📒 Files selected for processing: 11
  • src/main/ipc/pty-agent-pane-launch.test.ts
  • src/main/ipc/pty-launch-parity.test-fixture.ts
  • src/main/ipc/pty-launch-parity.test.ts
  • src/main/ipc/pty/ipc/spawn-env-codex.ts
  • src/main/ipc/pty/ipc/spawn-preflight.ts
  • src/main/ipc/pty/runtime/spawn-preflight.ts
  • src/main/ipc/pty/spawn-startup-target.ts
  • src/renderer/src/components/terminal-pane/launch-parity-window-tab.test.ts
  • src/renderer/src/lib/launch-parity-workspace-start.test-fixture.ts
  • src/shared/launch-parity-window-request.test-fixture.ts
  • src/shared/launch-parity-workspace-start-cases.test-fixture.ts
💤 Files with no reviewable changes: 1
  • src/main/ipc/pty/ipc/spawn-env-codex.ts

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

📝 Walkthrough

The change adds a shared PTY startup-target resolver and uses it in runtime and IPC preflight. The resolver returns startup CWD, terminal runtime options, WSL distro, and Codex selection target, with optional missing-CWD fallback metadata. Tests compare window and host preflight behavior. Launch-parity fixtures and renderer tests now cover work-item and folder-composer workspace starts across operating systems and shell runtimes.

Priority: ➖ Normal

Merge Risk: ⚪ Minimal · up to cbe11

No merge-blocking behavior change was identified; this is ready for normal merge checks.

Pre-merge checks: Passed 3 · Failed 2

Failed Failed checks: 2 warnings

Check name Status Explanation Resolution
Description check Warning The description covers the change, rationale, testing, platform considerations, visual proof, and checklist. However, the required Linked Issue section states that no issue exists, which does not meet… Add a valid linked issue under the Linked Issue section. If the author is a maintainer and the repository permits the documented exception, state that exception clearly instead of writing that no issue exists.
Docstring Coverage Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 12 functions across 10 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 3 passed
Check name Status Explanation
Title check Passed The title clearly and concisely describes the main change: consolidating PTY spawn preflight into one shared step for both launch lanes.
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.
Full details: Description check

Explanation

The description covers the change, rationale, testing, platform considerations, visual proof, and checklist. However, the required Linked Issue section states that no issue exists, which does not meet the repository template requirement for an actual issue link.

  • 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
brennanb2025 merged commit 5b7845e into main Oct 15, 2026
82 of 84 checks passed
@brennanb2025
brennanb2025 deleted the brennanb2025/conv-c5b-shared-spawn-preflight branch October 15, 2026 23:24
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