diff --git a/.github/workflows/computer-e2e.yml b/.github/workflows/computer-e2e.yml
index 784172d699e..3023d414abe 100644
--- a/.github/workflows/computer-e2e.yml
+++ b/.github/workflows/computer-e2e.yml
@@ -117,7 +117,6 @@ jobs:
- run: pnpm verify:computer-native
- run: pnpm build:cli
- run: pnpm build:electron-vite
- - run: pnpm verify:cli-runtime
# Why: boot the BUILT daemon-entry under plain Node the way production
# forks it. v1.4.129-rc.1 shipped a daemon that exited at module load
# (leaked electron require) while every other check passed; this fails
@@ -159,7 +158,6 @@ jobs:
- run: pnpm verify:computer-native
- run: pnpm build:cli
- run: pnpm build:electron-vite
- - run: pnpm verify:cli-runtime
- run: pnpm test:e2e:computer --reporter=verbose tests/e2e/computer-mac.e2e.ts tests/e2e/computer-mac-safari.e2e.ts
linux:
@@ -187,7 +185,6 @@ jobs:
- run: pnpm verify:computer-native
- run: pnpm build:cli
- run: pnpm build:electron-vite
- - run: pnpm verify:cli-runtime
- run: xvfb-run --auto-servernum dbus-run-session -- pnpm test:e2e:computer --reporter=verbose tests/e2e/computer-linux.e2e.ts
windows:
@@ -207,5 +204,4 @@ jobs:
- run: pnpm verify:computer-native
- run: pnpm build:cli
- run: pnpm build:electron-vite
- - run: pnpm verify:cli-runtime
- run: pnpm test:e2e:computer --reporter=verbose tests/e2e/computer-windows.e2e.ts tests/e2e/computer-windows-store.e2e.ts
diff --git a/README.md b/README.md
index aa4ee905281..0c8fb045410 100644
--- a/README.md
+++ b/README.md
@@ -36,7 +36,7 @@
Monitor and steer your agents from your phone — get notified when an agent finishes and send follow-ups from anywhere.
-[iOS App Store](https://apps.apple.com/us/app/orca-ide/id6766130217) · [TestFlight](https://testflight.apple.com/join/YjeGMQBA) · [Android APK 0.0.31](https://github.com/stablyai/orca/releases/download/mobile-android-v0.0.31/app-release.apk) · [Docs →](https://www.onorca.dev/docs/mobile)
+[iOS App Store](https://apps.apple.com/us/app/orca-ide/id6766130217) · [TestFlight](https://testflight.apple.com/join/YjeGMQBA) · [Android APK 0.0.32](https://github.com/stablyai/orca/releases/download/mobile-android-v0.0.32/app-release.apk) · [Docs →](https://www.onorca.dev/docs/mobile)
@@ -230,7 +230,7 @@ yay -S stably-orca-bin
Pair with your desktop app to monitor and steer your agents from your phone.
- **iOS:** [Download on the App Store](https://apps.apple.com/us/app/orca-ide/id6766130217) or [join TestFlight](https://testflight.apple.com/join/YjeGMQBA)
-- **Android:** [Download APK 0.0.31](https://github.com/stablyai/orca/releases/download/mobile-android-v0.0.31/app-release.apk)
+- **Android:** [Download APK 0.0.32](https://github.com/stablyai/orca/releases/download/mobile-android-v0.0.32/app-release.apk)
---
diff --git a/config/reliability-gates.jsonc b/config/reliability-gates.jsonc
index 7ec61073b60..77b41ee22ff 100644
--- a/config/reliability-gates.jsonc
+++ b/config/reliability-gates.jsonc
@@ -1,6 +1,6 @@
{
"schemaVersion": 1,
- "updatedAt": "2026-07-20",
+ "updatedAt": "2026-07-22",
"policy": {
"maturityLevels": [
"experimental",
@@ -2552,11 +2552,107 @@
],
"demotionRule": "Cannot promote while Windows E2E is flaky, silently skipped, or screenshot-only."
},
+ {
+ "id": "terminal-performance.cold-restore-replay-budget",
+ "title": "Daemon cold restore keeps replay work and retained payloads bounded",
+ "maturity": "experimental",
+ "protection": "partial",
+ "owner": "terminal-runtime",
+ "layer": "main-daemon-unit",
+ "surfaces": [
+ "startup restore",
+ "daemon history replay",
+ "sleep and hibernation restore",
+ "main-process memory"
+ ],
+ "platforms": ["macos", "linux", "windows"],
+ "providers": ["daemon", "wsl"],
+ "coveredPlatforms": ["macos"],
+ "coveredProviders": ["daemon"],
+ "coverageNotes": "Deterministic main-process tests cover byte-bounded cache eviction and ACK release, one-at-a-time replay admission, a fixed per-turn replay budget within one large output record, UTF-16 boundary preservation, and checkpoint-only restore bypass while another replay is paused. The same HistoryReader path carries WSL context, but live WSL and cross-platform startup-scale runs remain gaps.",
+ "motivatingLinks": [
+ "https://github.com/stablyai/orca/issues/9971",
+ "https://github.com/stablyai/orca/pull/9990",
+ "https://github.com/stablyai/orca/issues/9441"
+ ],
+ "invariant": "Cold restore must reproduce persisted terminal output while admitting at most one scratch-emulator replay, yielding after at most 64 Ki UTF-16 code units or 1,024 replay operations, keeping sticky restore payloads within 16 MiB, and allowing header-only checkpoint restores to bypass the replay queue.",
+ "oracle": "Pause setImmediate during two single-batch restores larger than one replay slice and require exactly one admitted yield at a time, preserved text across a surrogate-pair slice boundary, and a concurrent header-only checkpoint restore to finish without consuming a replay slot. Cache tests require least-recently-used eviction, rejection of one oversized payload, and zero retained cache bytes after renderer ACK.",
+ "commands": [
+ "pnpm exec vitest run --config config/vitest.config.ts src/main/daemon/cold-restore-payload-cache.test.ts src/main/daemon/history-reader.test.ts src/main/daemon/terminal-history-incremental-restore.test.ts src/main/daemon/hibernation-cold-restore-repro.test.ts src/main/daemon/daemon-pty-adapter.test.ts"
+ ],
+ "testFiles": [
+ "src/main/daemon/cold-restore-payload-cache.test.ts",
+ "src/main/daemon/history-reader.test.ts",
+ "src/main/daemon/terminal-history-incremental-restore.test.ts",
+ "src/main/daemon/hibernation-cold-restore-repro.test.ts",
+ "src/main/daemon/daemon-pty-adapter.test.ts"
+ ],
+ "assertionRefs": [
+ {
+ "file": "src/main/daemon/terminal-history-incremental-restore.test.ts",
+ "assertions": [
+ "large single-batch replays yield within the record, preserve a surrogate pair at the slice boundary, and admit only one scratch replay at a time",
+ "a header-only checkpoint restore completes while an unrelated incremental replay is paused"
+ ]
+ },
+ {
+ "file": "src/main/daemon/cold-restore-payload-cache.test.ts",
+ "assertions": [
+ "least-recently-used payloads are evicted to the aggregate byte budget",
+ "one payload larger than the entire budget is not retained"
+ ]
+ },
+ {
+ "file": "src/main/daemon/daemon-pty-adapter.test.ts",
+ "assertions": [
+ "StrictMode remount receives sticky cold-restore data until renderer ACK clears its retained bytes"
+ ]
+ }
+ ],
+ "evidenceRuns": [
+ {
+ "date": "2026-07-22",
+ "runner": "local",
+ "platform": "macos",
+ "command": "pnpm exec vitest run --config config/vitest.config.ts src/main/daemon/cold-restore-payload-cache.test.ts src/main/daemon/history-reader.test.ts src/main/daemon/terminal-history-incremental-restore.test.ts src/main/daemon/hibernation-cold-restore-repro.test.ts src/main/daemon/daemon-pty-adapter.test.ts",
+ "result": "passed",
+ "durationSeconds": 5.68,
+ "summary": "Five focused files passed 150 tests, including deterministic single-record replay slicing, one-at-a-time admission, UTF-16 boundary preservation, header-only queue bypass, byte-bounded LRU eviction, and ACK cleanup."
+ }
+ ],
+ "runtimeBudget": {
+ "p95Seconds": 15,
+ "scope": "focused main-process cold-restore unit contract"
+ },
+ "flakeHistory": {
+ "status": "unknown",
+ "evidence": "The focused deterministic slice is new and has no CI or soak history yet."
+ },
+ "redGreenEvidence": {
+ "status": "partial",
+ "evidence": "The prior implementation had no yield inside one large batch and queued header-only restores behind the shared semaphore by inspection; an intentional pre-fix test run was not recorded."
+ },
+ "performanceBudget": {
+ "required": true,
+ "evidence": "Production admits one emulator replay globally, yields after a deterministic 64 Ki character or 1,024-operation budget even within one record, bypasses the semaphore for the common header-only final-checkpoint path, and caps sticky payloads at 16 MiB. No polling, subprocess, session inventory, or renderer wake loop is added."
+ },
+ "promotionCriteria": [
+ "Record an intentional-break red run for both the within-record yield and header-only bypass assertions.",
+ "Collect startup event-loop-delay evidence with dozens of near-cap histories on representative macOS, Windows, and Linux hardware.",
+ "Add live WSL restore evidence before claiming WSL coverage."
+ ],
+ "knownGaps": [
+ "The log decoder and final headless snapshot serialization remain synchronous inside the one-at-a-time replay slot; the gate bounds replay writes, not every CPU phase.",
+ "No live Electron startup-scale run currently proves first-pane paint order or end-to-end restore latency with dozens of histories.",
+ "SSH, remote-runtime, relay, and mobile do not use this local daemon history reader and are unaffected."
+ ],
+ "demotionRule": "Keep experimental or demote to protection none if output differs across replay slices, header-only restores consume a replay slot, retained payload bytes exceed the cap, or the focused gate flakes."
+ },
{
"id": "terminal-performance.no-hot-list-sessions",
"title": "Hot terminal interactions do not call global PTY session listing",
"maturity": "experimental",
- "protection": "none",
+ "protection": "partial",
"owner": "terminal-performance",
"layer": "ipc-count-contract",
"surfaces": [
@@ -2580,19 +2676,74 @@
"wsl",
"remote-runtime"
],
- "coveredPlatforms": [],
+ "coveredPlatforms": [
+ "macos"
+ ],
"coveredProviders": [],
- "coverageNotes": "Registered gap on main. The targeted-hasPty product hardening and no-hot count assertions exist only on the pending reliability stack. It registers here with its owning split PR.",
+ "coverageNotes": "Platform-neutral unit coverage proves the Resource Manager closed badge performs one readiness seed, coalesces unknown spawn signals, skips known-session reattach signals, and installs no interval. Broader terminal interaction coverage remains on the pending reliability stack.",
"motivatingLinks": [
"https://github.com/stablyai/orca/pull/7002",
- "https://github.com/stablyai/orca/pull/6858"
+ "https://github.com/stablyai/orca/pull/6858",
+ "https://github.com/stablyai/orca/issues/9386",
+ "https://github.com/stablyai/orca/pull/9387"
],
"invariant": "Typing, focus, terminal switch, workspace switch, visibility resume, resize, render, and per-pane liveness paths must not call global pty:listSessions; they must use targeted per-PTY APIs or cached provider-owned state.",
- "oracle": "The current executable slice asserts targeted visibility/first-input liveness, resize re-assertion after visibility resume, light tab/active-state resume, SSH/remote skip behavior, and closed Resource Manager status badges avoid pty:listSessions; targeted hasPty/getSize calls are allowed for liveness/resize slices and forbidden for light tab/active-state resume. The full hot-path oracle still needs instrumentation around raw focus, split focus, workspace switch, render ticks, and high-session local/daemon/SSH fixtures.",
- "commands": [],
- "testFiles": [],
- "assertionRefs": [],
- "evidenceRuns": [],
+ "oracle": "The current executable slice asserts targeted visibility/first-input liveness, resize re-assertion after visibility resume, light tab/active-state resume, SSH/remote skip behavior, and a closed Resource Manager budget of one readiness seed plus one coalesced inventory read only for unknown spawn IDs; known-session reattach signals and steady closed time perform zero reads. Targeted hasPty/getSize calls are allowed for liveness/resize slices and forbidden for light tab/active-state resume. The full hot-path oracle still needs instrumentation around raw focus, split focus, workspace switch, render ticks, and high-session local/daemon/SSH fixtures.",
+ "commands": [
+ "pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/pty.test.ts src/renderer/src/components/status-bar/use-resource-session-inventory.test.tsx src/renderer/src/components/status-bar/resource-session-inventory.test.ts src/renderer/src/components/status-bar/ResourceUsageStatusSegment.session-polling.test.ts"
+ ],
+ "testFiles": [
+ "src/main/ipc/pty.test.ts",
+ "src/renderer/src/components/status-bar/use-resource-session-inventory.test.tsx",
+ "src/renderer/src/components/status-bar/resource-session-inventory.test.ts",
+ "src/renderer/src/components/status-bar/ResourceUsageStatusSegment.session-polling.test.ts"
+ ],
+ "assertionRefs": [
+ {
+ "file": "src/renderer/src/components/status-bar/use-resource-session-inventory.test.tsx",
+ "assertions": [
+ "the false-to-true workspace readiness transition performs one daemon inventory seed",
+ "a failed readiness seed surfaces an error and a later inventory refresh recovers",
+ "known-session reattach signals perform zero additional inventory reads",
+ "multiple unknown background spawn signals coalesce to one inventory read",
+ "spawn signals during a slow inventory read never overlap provider-wide scans and cause at most one required follow-up",
+ "unknown sessions that exit before reconciliation cancel their queued inventory read",
+ "unmount during a slow inventory read cannot schedule follow-up work",
+ "exit and out-of-order refresh races cannot resurrect stale sessions"
+ ]
+ },
+ {
+ "file": "src/main/ipc/pty.test.ts",
+ "assertions": [
+ "global inventory starts local and SSH provider listings concurrently"
+ ]
+ },
+ {
+ "file": "src/renderer/src/components/status-bar/resource-session-inventory.test.ts",
+ "assertions": [
+ "daemon inventory construction copies its source and preserves count parity",
+ "single and batch removals preserve unrelated sessions and no-op references"
+ ]
+ },
+ {
+ "file": "src/renderer/src/components/status-bar/ResourceUsageStatusSegment.session-polling.test.ts",
+ "assertions": [
+ "the closed inventory hook installs no interval",
+ "the badge count comes from cached daemon inventory rather than wake-hint bindings"
+ ]
+ }
+ ],
+ "evidenceRuns": [
+ {
+ "date": "2026-07-22",
+ "runner": "local",
+ "platform": "macos",
+ "command": "pnpm exec vitest run --config config/vitest.config.ts src/main/ipc/pty.test.ts src/renderer/src/components/status-bar/use-resource-session-inventory.test.tsx src/renderer/src/components/status-bar/resource-session-inventory.test.ts src/renderer/src/components/status-bar/ResourceUsageStatusSegment.session-polling.test.ts",
+ "result": "passed",
+ "durationSeconds": 4.3,
+ "summary": "4 files and 358 tests passed, covering readiness seed/recovery, zero interval polling, bounded unknown-spawn reconciliation, concurrent provider starts, exit fencing, cleanup, and out-of-order refresh fencing."
+ }
+ ],
"runtimeBudget": {
"p95Seconds": 20,
"scope": "unit or focused Electron count gate"
@@ -2603,7 +2754,7 @@
},
"redGreenEvidence": {
"status": "partial",
- "evidence": "Tests assert visibility resume prefers targeted hasPty over listSessions, first input after visibility resume calls targeted hasPty once, resize re-assertion after visibility resume uses getSize/resize without listSessions, light tab switches and visible active-state resume avoid listSessions/hasPty/getSize fanout while still allowing the active PTY scheduler hint, SSH/remote broad listing is skipped, Resource Manager broad session inventory polling is scoped to the open popover rather than its closed badge, and panes close only on authoritative false. Needs broader raw focus/workspace-switch/render/high-session count coverage before promotion."
+ "evidence": "Tests assert visibility resume prefers targeted hasPty over listSessions, first input after visibility resume calls targeted hasPty once, resize re-assertion after visibility resume uses getSize/resize without listSessions, light tab switches and visible active-state resume avoid listSessions/hasPty/getSize fanout while still allowing the active PTY scheduler hint, SSH/remote broad listing is skipped, and the closed Resource Manager performs one readiness seed while known reattach signals and steady time perform no additional reads. Needs broader raw focus/workspace-switch/render/high-session count coverage before promotion."
},
"performanceBudget": {
"required": true,
@@ -2615,8 +2766,8 @@
"Run with enough preserved sessions/providers to make a broad listing observable."
],
"knownGaps": [
- "No executable coverage on main yet; the slice lives on the pending fix-terminal-reliability stack.",
- "Current command covers targeted visibility/first-input liveness, resize re-assertion on visibility resume, light tab/active-state resume, SSH/remote skip behavior, and closed Resource Manager session-poll avoidance, but not every hot interaction listed in the invariant.",
+ "Current commands cover Resource Manager readiness/lifecycle inventory counts; the broader targeted-liveness slice still lives on the pending fix-terminal-reliability stack.",
+ "Current coverage includes the closed Resource Manager's no-interval and known-reattach budgets, but not every hot interaction listed in the invariant.",
"No Electron or IPC-level high-session counter gate yet proves raw focus, workspace switch, render, or high-session typing stay at zero global listSessions calls."
],
"demotionRule": "Cannot promote if the test allows broad listing in any hot interaction path."
diff --git a/config/scripts/claude-account-windows-spawn-repro.mjs b/config/scripts/claude-account-windows-spawn-repro.mjs
new file mode 100644
index 00000000000..eaf04762e5b
--- /dev/null
+++ b/config/scripts/claude-account-windows-spawn-repro.mjs
@@ -0,0 +1,342 @@
+import { spawn } from 'node:child_process'
+import { mkdtemp, mkdir, readFile, rm, writeFile } from 'node:fs/promises'
+import { tmpdir } from 'node:os'
+import { join } from 'node:path'
+import { buildWindowsCommandInvocation } from '../../src/main/claude-accounts/windows-command-invocation.ts'
+
+const strategy = process.argv[2]
+if (!['baseline', 'candidate', 'explicit-cmd'].includes(strategy)) {
+ throw new Error(
+ 'Usage: node config/scripts/claude-account-windows-spawn-repro.mjs '
+ )
+}
+if (process.platform !== 'win32') {
+ throw new Error('This reproduction requires a physical Windows host.')
+}
+
+const expectedArgs = [
+ '',
+ 'two words',
+ 'amp&ersand',
+ 'pipe|value',
+ 'lessvalue',
+ 'caret^value',
+ 'trailing\\',
+ 'two-trailing\\\\',
+ '(parentheses)',
+ '100%',
+ '%ORCA_ARG_TRAP%',
+ 'bang!value',
+ '한글-λ'
+]
+const tempRoot = await mkdtemp(join(tmpdir(), 'orca-claude-spawn-'))
+const reportedDir = join(tempRoot, 'Profile with spaces 한글')
+const reportedCapturePath = join(reportedDir, 'capture.json')
+const reportedPidPath = join(reportedDir, 'pids.json')
+const reportedShimPath = join(reportedDir, 'claude fixture.cmd')
+const reportedFixturePath = join(reportedDir, 'capture-child.cjs')
+const fixtureDir = join(tempRoot, 'Profile space & ^ (paren) %ORCA_PATH_TRAP% !bang! 한글')
+const capturePath = join(fixtureDir, 'capture.json')
+const pidPath = join(fixtureDir, 'pids.json')
+const shimPath = join(fixtureDir, 'claude fixture.cmd')
+const fixturePath = join(fixtureDir, 'capture-child.cjs')
+const fixtureEnv = {
+ ...process.env,
+ CLAUDE_CONFIG_DIR: join(fixtureDir, 'config space & ^ (paren) %ORCA_ENV_LITERAL% !bang! 한글'),
+ ORCA_ARG_TRAP: 'EXPANDED_ARG',
+ ORCA_PATH_TRAP: 'EXPANDED_PATH',
+ ORCA_FIXTURE_CAPTURE: capturePath,
+ ORCA_FIXTURE_PIDS: pidPath,
+ ORCA_FIXTURE_NODE: process.execPath
+}
+const reportedEnv = {
+ ...fixtureEnv,
+ CLAUDE_CONFIG_DIR: join(reportedDir, 'config with spaces 한글'),
+ ORCA_FIXTURE_CAPTURE: reportedCapturePath,
+ ORCA_FIXTURE_PIDS: reportedPidPath
+}
+
+function quoteForCandidate(value) {
+ return `"${value.replace(/"/g, '""')}"`
+}
+
+function launch(args, command = shimPath, env = fixtureEnv) {
+ if (strategy === 'baseline') {
+ return spawn(command, args, { cwd: tempRoot, env, shell: true, windowsHide: true })
+ }
+ if (strategy === 'candidate') {
+ return spawn(quoteForCandidate(command), args, {
+ cwd: tempRoot,
+ env,
+ shell: true,
+ windowsHide: true
+ })
+ }
+ const invocation = buildWindowsCommandInvocation(command, args)
+ return spawn(invocation.command, invocation.args, {
+ cwd: tempRoot,
+ env,
+ shell: false,
+ windowsVerbatimArguments: invocation.windowsVerbatimArguments,
+ windowsHide: true
+ })
+}
+
+function collect(child) {
+ return new Promise((resolve) => {
+ let stdout = ''
+ let stderr = ''
+ child.stdout?.on('data', (chunk) => (stdout += chunk.toString()))
+ child.stderr?.on('data', (chunk) => (stderr += chunk.toString()))
+ child.on('error', (error) => resolve({ code: null, stdout, stderr, error: error.message }))
+ child.on('close', (code) => resolve({ code, stdout, stderr, error: null }))
+ })
+}
+
+async function waitForFile(path, timeoutMs = 5_000) {
+ const deadline = Date.now() + timeoutMs
+ while (Date.now() < deadline) {
+ try {
+ return JSON.parse(await readFile(path, 'utf8'))
+ } catch {
+ await new Promise((resolve) => setTimeout(resolve, 25))
+ }
+ }
+ throw new Error(`Timed out waiting for fixture output: ${path}`)
+}
+
+async function taskExists(pid) {
+ const result = await collect(
+ spawn('tasklist.exe', ['/fi', `PID eq ${pid}`, '/fo', 'csv', '/nh'], {
+ windowsHide: true
+ })
+ )
+ if (result.error || result.code !== 0) {
+ throw new Error(`tasklist failed for PID ${pid}: ${result.error ?? result.stderr}`)
+ }
+ return result.stdout.includes(`"${pid}"`)
+}
+
+async function killTree(pid) {
+ const result = await collect(
+ spawn('taskkill.exe', ['/pid', String(pid), '/t', '/f'], { windowsHide: true })
+ )
+ if (result.error || result.code !== 0) {
+ throw new Error(`taskkill failed for PID ${pid}: ${result.error ?? result.stderr}`)
+ }
+}
+
+async function waitForTreeExit(pids, timeoutMs = 5_000) {
+ const deadline = Date.now() + timeoutMs
+ let alive = {}
+ do {
+ alive = Object.fromEntries(
+ await Promise.all(
+ Object.entries(pids).map(async ([name, pid]) => [name, await taskExists(pid)])
+ )
+ )
+ if (!Object.values(alive).some(Boolean)) {
+ return alive
+ }
+ await new Promise((resolve) => setTimeout(resolve, 50))
+ } while (Date.now() < deadline)
+ return alive
+}
+
+const results = {
+ strategy,
+ reportedPath: null,
+ pathMatrix: {},
+ argvMatrix: {},
+ hostilePathAndArgv: null,
+ error: null,
+ cancellation: null
+}
+const fixtureSource =
+ `const { spawn } = require('node:child_process')\n` +
+ `const { writeFileSync } = require('node:fs')\n` +
+ `if (process.argv[2] === '--exit-error') { process.stderr.write('fixture error: 한글 & ^ % !\\n'); process.exit(23) }\n` +
+ `if (process.argv[2] === '--linger') {\n` +
+ ` const grandchild = spawn(process.execPath, ['-e', 'setInterval(() => {}, 1000)'], { windowsHide: true })\n` +
+ ` writeFileSync(process.env.ORCA_FIXTURE_PIDS, JSON.stringify({ child: process.pid, grandchild: grandchild.pid }))\n` +
+ ` setInterval(() => {}, 1000)\n` +
+ `} else {\n` +
+ ` writeFileSync(process.env.ORCA_FIXTURE_CAPTURE, JSON.stringify({ argv: process.argv.slice(2), configDir: process.env.CLAUDE_CONFIG_DIR }))\n` +
+ `}\n`
+const shimSource = '@echo off\r\n"%ORCA_FIXTURE_NODE%" "%~dp0capture-child.cjs" %*\r\n'
+let lingeringShellPid = null
+try {
+ await mkdir(fixtureDir, { recursive: true })
+ await mkdir(reportedDir, { recursive: true })
+ await writeFile(fixturePath, fixtureSource, 'utf8')
+ await writeFile(shimPath, shimSource, 'utf8')
+ await writeFile(reportedFixturePath, fixtureSource, 'utf8')
+ await writeFile(reportedShimPath, shimSource, 'utf8')
+
+ const reportedArgs = ['auth', 'status', '--json']
+ const reportedRun = await collect(launch(reportedArgs, reportedShimPath, reportedEnv))
+ let reportedCapture = null
+ try {
+ reportedCapture = await waitForFile(reportedCapturePath, 1_000)
+ } catch {}
+ results.reportedPath = {
+ ...reportedRun,
+ actual: reportedCapture,
+ expected: { argv: reportedArgs, configDir: reportedEnv.CLAUDE_CONFIG_DIR },
+ pass:
+ reportedRun.code === 0 &&
+ JSON.stringify(reportedCapture) ===
+ JSON.stringify({ argv: reportedArgs, configDir: reportedEnv.CLAUDE_CONFIG_DIR })
+ }
+
+ for (const [name, segment] of Object.entries({
+ spaces: 'profile space',
+ ampersand: 'profile&name',
+ caret: 'profile^name',
+ parentheses: 'profile(name)',
+ percent: 'profile%ORCA_PATH_TRAP%',
+ bang: 'profile!name',
+ unicode: 'profile-한글-λ'
+ })) {
+ const directory = join(tempRoot, segment)
+ const captureFile = join(directory, 'capture.json')
+ const command = join(directory, 'claude fixture.cmd')
+ await mkdir(directory, { recursive: true })
+ await writeFile(join(directory, 'capture-child.cjs'), fixtureSource, 'utf8')
+ await writeFile(command, shimSource, 'utf8')
+ const env = {
+ ...fixtureEnv,
+ CLAUDE_CONFIG_DIR: join(directory, 'config'),
+ ORCA_FIXTURE_CAPTURE: captureFile
+ }
+ const run = await collect(launch(reportedArgs, command, env))
+ let actual = null
+ try {
+ actual = await waitForFile(captureFile, 500)
+ } catch {}
+ results.pathMatrix[name] = {
+ code: run.code,
+ stderr: run.stderr.trim(),
+ actual: actual?.argv ?? null,
+ pass: run.code === 0 && JSON.stringify(actual?.argv) === JSON.stringify(reportedArgs)
+ }
+ }
+
+ for (const [name, value] of Object.entries({
+ empty: '',
+ spaces: 'two words',
+ ampersand: 'amp&ersand',
+ pipe: 'pipe|value',
+ lessThan: 'lessvalue',
+ caret: 'caret^value',
+ trailingBackslash: 'trailing\\',
+ twoTrailingBackslashes: 'two-trailing\\\\',
+ parentheses: '(parentheses)',
+ percent: '%ORCA_ARG_TRAP%',
+ bang: 'bang!value',
+ unicode: '한글-λ'
+ })) {
+ const args = ['prefix', value, 'suffix']
+ const captureFile = join(reportedDir, `capture-${name}.json`)
+ const env = { ...reportedEnv, ORCA_FIXTURE_CAPTURE: captureFile }
+ const run = await collect(launch(args, reportedShimPath, env))
+ let actual = null
+ try {
+ actual = await waitForFile(captureFile, 500)
+ } catch {}
+ results.argvMatrix[name] = {
+ code: run.code,
+ stderr: run.stderr.trim(),
+ actual: actual?.argv ?? null,
+ pass: run.code === 0 && JSON.stringify(actual?.argv) === JSON.stringify(args)
+ }
+ }
+
+ const argvRun = await collect(launch(expectedArgs))
+ let capture = null
+ try {
+ capture = await waitForFile(capturePath, 1_000)
+ } catch {}
+ results.hostilePathAndArgv = {
+ ...argvRun,
+ actual: capture,
+ expected: { argv: expectedArgs, configDir: fixtureEnv.CLAUDE_CONFIG_DIR },
+ pass:
+ argvRun.code === 0 &&
+ JSON.stringify(capture) ===
+ JSON.stringify({ argv: expectedArgs, configDir: fixtureEnv.CLAUDE_CONFIG_DIR })
+ }
+
+ results.error = await collect(launch(['--exit-error'], reportedShimPath, reportedEnv))
+ results.error.pass =
+ results.error.code === 23 && results.error.stderr.includes('fixture error: 한글 & ^ % !')
+
+ const lingering = launch(['--linger'], reportedShimPath, reportedEnv)
+ lingeringShellPid = lingering.pid
+ const lingeringResult = collect(lingering)
+ try {
+ const pids = await waitForFile(reportedPidPath)
+ await killTree(lingering.pid)
+ const alive = await waitForTreeExit({
+ shell: lingering.pid,
+ child: pids.child,
+ grandchild: pids.grandchild
+ })
+ results.cancellation = {
+ shell: lingering.pid,
+ ...pids,
+ alive,
+ pass: !Object.values(alive).some(Boolean)
+ }
+ } catch (error) {
+ if (await taskExists(lingering.pid)) {
+ await killTree(lingering.pid)
+ }
+ const launchResult = await Promise.race([
+ lingeringResult,
+ new Promise((resolve) =>
+ setTimeout(
+ () => resolve({ code: null, error: 'fixture did not exit after cleanup' }),
+ 5_000
+ )
+ )
+ ])
+ results.cancellation = {
+ shell: lingering.pid,
+ launchResult,
+ pass: false,
+ error: error instanceof Error ? error.message : String(error)
+ }
+ }
+} finally {
+ if (lingeringShellPid && (await taskExists(lingeringShellPid))) {
+ await killTree(lingeringShellPid)
+ }
+ try {
+ let pids
+ try {
+ pids = JSON.parse(await readFile(reportedPidPath, 'utf8'))
+ } catch {
+ pids = JSON.parse(await readFile(pidPath, 'utf8'))
+ }
+ for (const pid of [pids.child, pids.grandchild]) {
+ if (await taskExists(pid)) {
+ await killTree(pid)
+ }
+ }
+ } catch {}
+ await rm(tempRoot, { recursive: true, force: true })
+}
+
+console.log(JSON.stringify(results, null, 2))
+process.exitCode =
+ results.reportedPath?.pass &&
+ Object.values(results.pathMatrix).every((result) => result.pass) &&
+ Object.values(results.argvMatrix).every((result) => result.pass) &&
+ results.hostilePathAndArgv?.pass &&
+ results.error?.pass &&
+ results.cancellation?.pass
+ ? 0
+ : 1
diff --git a/config/scripts/computer-e2e-workflow.test.mjs b/config/scripts/computer-e2e-workflow.test.mjs
index 560859c5264..3ef41583217 100644
--- a/config/scripts/computer-e2e-workflow.test.mjs
+++ b/config/scripts/computer-e2e-workflow.test.mjs
@@ -177,7 +177,7 @@ describe('computer-use e2e workflow', () => {
)
})
- it('builds Electron main output and verifies the CLI before every computer-use e2e run', () => {
+ it('builds Electron main output before every computer-use e2e run', () => {
const workflow = parse(
readFileSync(join(projectDir, '.github/workflows/computer-e2e.yml'), 'utf8')
)
@@ -187,7 +187,6 @@ describe('computer-use e2e workflow', () => {
.map((step) => step.run)
.filter((run) => typeof run === 'string')
const buildIndex = runs.indexOf('pnpm build:electron-vite')
- const cliVerifyIndex = runs.indexOf('pnpm verify:cli-runtime')
const e2eIndexes = runs
.map((run, index) => (run.includes('test:e2e:computer') ? index : -1))
.filter((index) => index >= 0)
@@ -196,17 +195,10 @@ describe('computer-use e2e workflow', () => {
buildIndex,
`${jobName} should build out/main before computer e2e`
).toBeGreaterThanOrEqual(0)
- expect(
- cliVerifyIndex,
- `${jobName} should verify the CLI after the main build`
- ).toBeGreaterThan(buildIndex)
for (const e2eIndex of e2eIndexes) {
expect(buildIndex, `${jobName} should build out/main before computer e2e`).toBeLessThan(
e2eIndex
)
- expect(cliVerifyIndex, `${jobName} should verify the CLI before computer e2e`).toBeLessThan(
- e2eIndex
- )
}
}
})
diff --git a/config/scripts/verify-cli-bin.test.mjs b/config/scripts/verify-cli-bin.test.mjs
index d26a603a148..da988064d05 100644
--- a/config/scripts/verify-cli-bin.test.mjs
+++ b/config/scripts/verify-cli-bin.test.mjs
@@ -47,14 +47,6 @@ describe('verifyPackageCliBin', () => {
})
})
- it('rejects a compiled CLI with a missing runtime dependency', () => {
- const { projectDir } = makeProjectWithCli(
- '#!/usr/bin/env node\nrequire("./missing-runtime-dependency")\n'
- )
-
- expect(() => verifyPackageCliBin({ projectDir, runHelp: true })).toThrow()
- })
-
it('rejects an empty package bin target', () => {
const { projectDir } = makeProjectWithCli('')
diff --git a/config/tsconfig.cli.json b/config/tsconfig.cli.json
index 53859a16a32..38086f07969 100644
--- a/config/tsconfig.cli.json
+++ b/config/tsconfig.cli.json
@@ -4,9 +4,6 @@
"../src/cli/**/*",
"../src/shared/**/*",
"../src/main/agent-state-file-reader.ts",
- "../src/main/agent-hooks/agent-hook-file-comparison.ts",
- "../src/main/agent-hooks/agent-hook-file-limits.ts",
- "../src/main/agent-hooks/agent-hook-sftp-text-reader.ts",
"../src/main/agent-hooks/hook-stdin-contract.ts",
"../src/main/agent-hooks/hook-config-write-path.ts",
"../src/main/agent-hooks/hooks-json-read.ts",
@@ -23,12 +20,13 @@
"../src/main/codex/codex-app-server-client.ts",
"../src/main/codex/codex-app-server-grant-bridge.ts",
"../src/main/codex/codex-app-server-grant-envelope.ts",
- "../src/main/codex/codex-app-server-grant-json.ts",
"../src/main/codex/codex-app-server-session.ts",
"../src/main/codex/codex-config-mirror.ts",
"../src/main/codex/codex-config-path-reference-rewrite.ts",
+ "../src/main/codex/codex-config-settings-preservation.ts",
+ "../src/main/codex/codex-config-settings-removal.ts",
+ "../src/main/codex/codex-config-settings-upsert.ts",
"../src/main/codex/codex-home-paths.ts",
- "../src/main/codex/codex-host-retry-deadlines.ts",
"../src/main/codex/codex-hook-identity.ts",
"../src/main/codex/codex-hook-trust-grant.ts",
"../src/main/codex/codex-managed-trust-reconciliation.ts",
@@ -40,8 +38,10 @@
"../src/main/codex/codex-user-hook-trust-rebase-client.ts",
"../src/main/codex/codex-user-hook-trust-rebase.ts",
"../src/main/codex/codex-wsl-hook-install-plan.ts",
- "../src/main/codex/codex-wsl-reconciliation-generations.ts",
+ "../src/main/codex/config-settings-baseline.ts",
+ "../src/main/codex/config-settings-conflict-resolution.ts",
"../src/main/codex/config-settings-promotion.ts",
+ "../src/main/codex/config-toml-key-path.ts",
"../src/main/codex/config-toml-line-scan.ts",
"../src/main/codex/config-toml-trust.ts",
"../src/main/codex/hook-service.ts",
@@ -49,14 +49,12 @@
"../src/main/codex-accounts/fs-utils.ts",
"../src/main/codex-accounts/wsl-codex-command.ts",
"../src/main/codex-cli/command.ts",
- "../src/main/codex-cli/nvm-version-directory-discovery.ts",
"../src/main/command-code/command-code-managed-script.ts",
"../src/main/command-code/hook-service.ts",
"../src/main/copilot/hook-service.ts",
"../src/main/cursor/hook-service.ts",
"../src/main/droid/hook-service.ts",
"../src/main/gemini/hook-service.ts",
- "../src/main/generated-node-bounded-file-reader.ts",
"../src/main/grok/hook-service.ts",
"../src/main/devin/hook-settings.ts",
"../src/main/devin/hook-service.ts",
diff --git a/config/vitest.config.ts b/config/vitest.config.ts
index 9e8da4051bf..7f6fde0f0ca 100644
--- a/config/vitest.config.ts
+++ b/config/vitest.config.ts
@@ -20,6 +20,7 @@ export default defineConfig({
'src/**/*.test.tsx',
'config/scripts/**/*.test.ts',
'config/scripts/**/*.test.mjs',
+ 'tools/**/*.test.mjs',
'tests/e2e/**/*.unit.test.ts'
],
// Why: the full suite runs heavy TS transforms plus real git/http fixtures;
diff --git a/docs/assets/readme-downloads.svg b/docs/assets/readme-downloads.svg
index 790c0df3df0..4d25959e53c 100644
--- a/docs/assets/readme-downloads.svg
+++ b/docs/assets/readme-downloads.svg
@@ -1,5 +1,5 @@
- |