Technical Documentation: stablyai/orca
ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference
Overview (README.md)
[](https://onOrca.dev) Orca
[Download Orca](https://onorca.dev/download)
Features
| ### Mobile Companion
Monitor and steer your agents from your phone — get notified when an agent finishes and send follow-ups from anywhere.
| iOS App Store · Android APK · Docs → | |
|---|---|
| ### Parallel Worktrees |
Fan one prompt across five agents, each in its own isolated git worktree — compare the results and merge the winner.
Docs → | |
| ### Terminal Splits
Ghostty-class terminals with WebGL rendering, infinite splits, and scrollback that survives restarts.
Docs → | |
| ### Design Mode
Click any UI element in a real Chromium window to send its HTML, CSS, and a cropped screenshot straight into your agent's prompt.
Docs → | |
| ### GitHub & Linear, Native
Browse PRs, issues, and project boards in-app — open a worktree from any task and review without a context switch.
Docs → | |
| ### SSH Worktrees
Run agents on a beefy remote box with full file editing, git, and terminals — auto-reconnect and port forwarding included.
Docs → | |
| ### Annotate AI Diffs
Drop comments on any diff line and ship them back to the agent — review, edit, and commit without leaving Orca.
Docs → | |
| ### Drag Files to Agents
VS Code's editor with autosave everywhere — drag files or images straight into an agent prompt.
Docs → | |
| ### Orca CLI
Agents drive Orca too — script every workflow with orca worktree create, snapshot, click, and fill.
Docs → | |
Also in the box:
- Quick open — Search across worktrees, files, agents, commands, and repo context without leaving your flow.
- Account switcher & usage tracking — See Claude and Codex usage and rate-limit resets, and hot-swap accounts without re-logging in.
- Rich repo previews — Preview Markdown, images, PDFs, and repo docs in the workspace.
- Computer Use — Let agents operate desktop apps and visible UI when a workflow needs real interaction.
- Notifications and unread state — Know when an agent finishes or needs attention, then mark threads unread to come back later.
- And many, many more — we ship daily, so this list is perpetually behind. The changelog is the real feature list.
Supported Agents
Works with any CLI agent — if it runs in a terminal, it runs in Orca.
Claude Code
Codex
Grok
Cursor
GitHub Copilot
Muse
DeepSeek Harness
ZCode
OpenCode
MiMo Code
Amp
OpenClaude
Antigravity
Pi
oh-my-pi
Hermes Agent
Devin
Goose
Auggie
Autohand Code
Charm
Cline
CodeBuddy
Codebuff
Freebuff
Command Code
Continue
Droid
Kilocode
Kimi
Kiro
Mistral Vibe
Qwen Code
Rovo Dev
+ any CLI agent
Install
Desktop — macOS, Windows, Linux
- Download from onOrca.dev
- Or grab a build directly: macOS Apple Silicon · macOS Intel · Windows (.exe) · Linux AppImage · All builds
Or via a package manager:
# macOS (Homebrew)
brew install --cask stablyai/orca/orca
# Arch Linux (AUR) — or stably-orca-git to build from source
yay -S stably-orca-binMobile Companion — iOS, Android
Pair with your desktop app to monitor and steer your agents from your phone.
- iOS: Download on the App Store
- Android: Download APK · Install guide
Community & Support
Discord: Join the community on Discord.
Twitter / X: Follow @orca_build for updates and announcements.
WeChat: Group 12 is full. Scan to join the Orca community WeChat group 13.
Feedback & Ideas: We ship fast. Missing something? Request a new feature.
Privacy: See the privacy & telemetry docs for what anonymous usage data Orca collects and how to opt out.
Show Support: Star this repo to follow along with our daily ships.
Developing
Want to contribute or run locally? See our CONTRIBUTING.md guide.
The relay that pairs the mobile app with a desktop host is also in this repository undercloud/, with a separate pnpm workspace and setup guide.
Signed Builds
Windows code signing sponored/provided by SignPath.io, certificate by SignPath Foundation.
License
Orca is free and open source under the MIT License.
Site README (`docs/site/README.md`)
@orca/docs
This package contains the product documentation and public media intended to
ship alongside Orca's source code.
Open-source product documentation for Orca, served at /docs (same URL shape as https://www.onorca.dev/docs).
This package is a self-contained Next.js app. It is intentionally not a root monorepo workspace member, so installing Electron app dependencies does not pull Next/fumadocs.
Local development
Use Node.js 24 and the pnpm version pinned in package.json. CI reads the
Node.js version from this package's engines.node field.
cd docs/site
pnpm install
pnpm devOpen http://localhost:3004/docs.
Linting
Run pnpm lint inside this directory. The isolated Oxlint
configuration keeps the native equivalents of the previous Next.js, React,
React hooks and accessibility checks at their existing severities, including
alt text on Next.js Image components. Generated Next.js and MDX output is
excluded. Oxlint also reports its default correctness warnings.
The migration does not provide equivalents for React's deprecated-API check,
React Compiler configuration/gating diagnostics, or Next.js's relativelocation.assign advisory. JSX variable-use bookkeeping is handled by Oxlint's
parser. Keep TypeScript and the production Next.js build in the validation
workflow; this is not a claim of complete ESLint rule parity.
Production build
cd docs/site
pnpm install
pnpm build
pnpm startpnpm start serves the production build on port 3004. Paths:
| Path | Purpose |
|---|---|
/ |
Redirects to /docs |
/docs |
Docs index |
/docs/... |
Nested doc pages from content/docs |
/docs/api/search |
Fumadocs search index |
/docs/og/... |
Per-page Open Graph image route |
Layout
content/docs/— MDX pages +meta.jsonnavigationpublic/docs/— docs-only media, logo, and favicon (GIFs, posters, screenshots)src/app/docs/— fumadocs routes, OG imagessrc/components/— docs-scoped chrome (header/footer/search), not marketing site
Updating content
Treat the documentation tree as a deliberate publication boundary. Before
importing source material, review the diff for internal references, credentials,
third-party media rights, and feature/version claims, then copy only approved
pages and assets. Keep the app shell and deployment configuration in this
repository so a docs-only pull request can be reviewed and built independently.
Same-domain routing
The docs app is a separate Vercel project (the docs zone) and keeps the
public /docs URL namespace. The marketing site remains the default zone forwww.onorca.dev; configure its Next/Vercel proxy with these beforeFiles
rewrites, replacing DOCS_ORIGIN with the docs project's production URL:
return {
beforeFiles: [
{
source: '/docs',
destination: `${DOCS_ORIGIN}/docs`
},
{
source: '/docs/:path*',
destination: `${DOCS_ORIGIN}/docs/:path*`
},
{
source: '/docs-static/:path*',
destination: `${DOCS_ORIGIN}/docs-static/:path*`
}
]
}/docs-static is the docs zone's assetPrefix; it prevents _next asset
collisions with the marketing zone. Keep the rewrites in the default zone and
use ordinary <a> links when navigating between zones. Do not addbasePath: '/docs' to this app: its route tree and Fumadocs baseUrl already
include /docs, so doing so would publish /docs/docs/... URLs. If a future
deployment needs basePath, first move the route tree to an unprefixedsrc/app/[[...slug]] shape and update every generated/link URL together.
Deploy (Vercel)
- Create a Vercel project with Root Directory unset (
.). The workflow
invokes Vercel fromdocs/site, so that directory is already the deployment
root; setting it again would resolvedocs/site/docs/site. Leave automatic
Git deployments disabled so this workflow remains the only deployment path. - Framework preset: Next.js. This package's
engines.nodeselects Node.js 24 for
Vercel builds and functions.vercel.jsonpins install (pnpm install --frozen-lockfile) and build (pnpm build) over the dashboard settings; this package has its own lockfile beside the root workspace. - Set GitHub Actions secrets (required by the production deploy job):
VERCEL_TOKENVERCEL_ORG_IDVERCEL_PROJECT_ID(docs project, not the marketing site)
- Protect the
docs-productionGitHub environment with required reviewers and
custom deployment branch policies formainandv*tags. The release-cut
dispatch runs frommain; the direct published-release fallback runs from a
stable tag, while the workflow still authorizes only exact stable tags. - Prepare the three rewrites above in the
www.onorca.devmarketing project,
but leave them disabled until the docs deployment is verified. A Vercel
custom domain cannot delegate only/docsby itself; the default zone must
proxy both page/API/media requests and/docs-staticassets. Keep the
marketing project's old docs routes available for rollback during the
transition; putting the proxy rules inbeforeFilesensures they win once
enabled. - Deploy a stable desktop tag that contains
docs/site, verify the docs
origin, then enable the marketing rewrites. Tags cut before this package was
added cannot bootstrap the docs project because production intentionally
checks out the tag's exact commit. Remove the old marketing docs routes in a
follow-up after the proxy is stable.
.github/workflows/docs.yml runs credential-free checks for every pull request.
It intentionally does not deploy PR previews: a PR-controlled build must not
receive Vercel credentials. A maintainer can add a separate trusted preview
workflow later. Production deploys only from an authorized stable desktop release: an exactvX.Y.Z tag, a published non-prerelease release, and thegithub-actions[bot] release author. Manual dispatch must run from the default
branch and name an existing release that meets the same checks. Mobile,
prerelease, draft, and human-authored releases are skipped. Fork pull requests
remain build-only because GitHub does not expose deployment secrets to fork
jobs.
Versioning
versioning.config.ts keeps the current unversioned /docs URLs stable while
reserving content/versions/ for future snapshots. Versioned routes are not
live yet; when they are needed, add the corresponding loader/routes and a
version entry. The release workflow can then deploy them without a trigger
change.
Isolation from the desktop app
- Own
package.json+pnpm-lock.yamlunderdocs/site/ - Not listed in the root
pnpm-workspace.yaml; its ownpnpm-workspace.yamlmakes it a separate workspace root and holds its pnpm settings, which pnpm 12 ignores under--ignore-workspace - Engineering notes remain in repo-root
docs/— do not confuse with this package
Audits Acknowledged Tab Retirement README (`docs/audits/acknowledged-tab-retirement/README.md`)
Acknowledged terminal-tab retirement
A host-requested whole-tab close can return closed: true while its row remains in orca-data.json. Once a repository's host topology revision is positive, the Store preserves host membership against a renderer save that merely omits a tab. The renderer acknowledges after its session flush; its graph removal can arrive later. The existing runtime fallback committed retirement only after that graph stopped owning the parent.
This fixes that acknowledgement ordering path relevant to #17344. It does not establish that every tab in that report followed this path or explain large RSS from the small persisted row alone.
Run
ORCA_BACKGROUND_LAUNCH=1 node docs/audits/acknowledged-tab-retirement/reproduce.mjsThe runner uses temporary Vite configurations and the repository's cross-platform process runner. It reverses only fix.patch in memory for the baseline; the checkout remains unchanged. Tests use temporary Store files and inert desktop/provider ports. No application window, shell, SSH connection, or external host is started.
The 20-case artifact fixture drives the actual renderer close and session builders, Store persistence/reload, and runtime close/graph methods. Eleven source regressions exercise actual Store/runtime boundaries, including provider exit and spawn. The baseline has 28 failing fixed-behavior assertions and 3 passing controls; the fix passes 31 cases. Results include source hashes, exact failed cases, process exit codes, and timeout status.
Ownership checks
The acknowledged-close path captures the original execution-host partition, persisted tab creation/generation, leaf/PTY bindings, and current process incarnations. It rechecks these after the renderer acknowledgement before committing the existing host retirement transaction. Original leaves may disappear during physical exit, and the tab's aggregate PTY or remote-session ID may promote an original surviving sibling. A new leaf, binding, incarnation, tab generation, or host is refused. Current pins are checked by the existing commit primitive; explicit force retains its existing meaning.
The process guard covers original persisted row, remote-session and leaf IDs, plus snapshot leaf/layout IDs. Thus a partial mobile snapshot cannot hide an original sibling's replacement. Missing runtime records alone are not treated as evidence of process death. The existing headless fallback still collects persisted SSH kill IDs before it commits; the added renderer-owned path issues no provider kill.
Controls include local and SSH-backed repositories and folder workspaces, unrelated host partitions, same-ID replacements, physical split exits, stale renderer saves/graphs, current pins, force, renderer refusal, and disk failure. The renderer-owned live snapshot can remain until its ordinary graph removal arrives; an older queued graph cannot restore it afterward.
Scope limits
- A direct renderer-only close of a never-bound local tab still sends no explicit host retirement request. That separate gap remains reproduced by a negative control.
- The new durable identity capture requires the legacy terminal row in
tabsByWorktree. Unified-only terminal metadata is outside this fix. - The existing retirement primitive propagates a failed disk flush, but its attempted rollback can be rebased against the staged newer topology revision. The failure control verifies rejection and the original disk row; it does not claim successful in-memory rollback.
- No wire fields, process-liveness verdicts, or membership-fence rules change. SSH persistence stays in its existing host partition; this does not reconcile the separate historical renderer/host partition divergence.
- The related original audit traced the branch in reported v1.4.192. Executable comparisons use current source with this patch reversed, not the historical packaged application.
Audits Auth Filesystem Wait Retention README (`docs/audits/auth-filesystem-wait-retention/README.md`)
Release aborted shared auth filesystem waits
When a filesystem operation remains pending, later Codex/Kimi quota polls reuse it and wait with new deadlines. The old waiter uses Promise.race for every poll. On the installed Electron runtime, each abandoned race retains its rejection reason until the raw operation settles, despite removing its abort listener. The fix uses the existing PromiseSettlementWaiters registry, which attaches one raw-result reaction and removes expired waiters.
The original ownership symbols, last-waiter cancellation finalizer, one-raw-operation behavior, live callers, and future reads of a late result are preserved. Auth opts into deferred abort settlement so an already-queued raw result keeps its Promise.race priority; 24 success/failure/abort schedules compare equal before and after. Existing registry consumers keep their immediate-abort behavior. The abort factory type accepts unknown so false, zero, strings, and objects retain the existing auth rejection semantics. No admission or timeout limit changes.
Reproduce
ORCA_BACKGROUND_LAUNCH=1 node --expose-gc --max-old-space-size=128 docs/audits/auth-filesystem-wait-retention/reproduce.cjsFor Electron, run the installed Electron executable with ELECTRON_RUN_AS_NODE=1 and the same arguments/environment. This launches no window. The proof reconstructs the original sources by reversing fix.patch; expected baseline hashes in source-versions.json make source drift fail. Both versions run the actual production scheduler/waiter code, with only the raw filesystem operation replaced by one manually settled promise. A 15-second deadline fails stalled proof execution.
| Runtime / source | Plain aborted Errors alive while raw result pending | Amplified payload objects alive | After raw result settles | After owner drops |
|---|---|---|---|---|
| Node 26.6.0 / original | 1 of 128 | 1 of 128 | 1 | 0 |
| Node 26.6.0 / fixed | 1 of 128 | 1 of 128 | 1 | 0 |
| Electron 43.7.0, Node 24.21.0 / original | 128 of 128 | 128 of 128 | 1 | 0 |
| Electron 43.7.0, Node 24.21.0 / fixed | 1 of 128 | 1 of 128 | 1 | 0 |
The one remaining reason belongs to the existing cancellation controller's first abort. Dropping the shared operation releases it. AbortController objects and abort listeners are released by both versions. The amplified arm attaches a synthetic 64 KiB Uint8Array to each Error, at most 8 MiB per case. Ordinary timeout errors are much smaller; the separate plain-Error arm verifies that artificial bytes are not needed to reproduce retention. Runtime differences are measured, without attributing them to a particular V8 change.
Controls check an already-aborted first caller never starting raw work, raw rejection identity, aborted caller identity, future callers receiving late and already-settled results, a live sibling surviving cancellation, arbitrary abort reasons, and removed listeners. The unit regression additionally checks that repeated expired waits add no raw-result reactions and that their plain Error objects are collectible while a live caller still needs the operation.
Validation: 50 auth/registry tests pass, including 18 new cases; the reverse-patch configuration produces two expected failures and 48 passes. Six existing watcher-consumer suites pass another 39 tests. Node, Web, and CLI typechecks, focused ordinary/type-aware lint, formatting, and the changed-code quality gate pass. validation.json records the test paths and scope. To run the prior implementation against the current tests, use --config docs/audits/auth-filesystem-wait-retention/before.config.mjs with those six auth/registry test paths.
Scope and limits
The three production consumers are codex-auth-presence.ts, codex-backend-auth.ts, and kimi-fetcher.ts. Each intentionally retains the shared operation until actual filesystem settlement to avoid stacking native requests when UNC/WSL reads stall. This audit does not reproduce a real filesystem stall, historical Electron binary behavior, or an affected-host workload.
The auth source matches v1.4.198 and the audited main revision; the existing registry also matches main. The earlier broad accumulator PR #10179, reverted by #10255, added path/waiter/admission limits to this module. This change instead removes abandoned wait reactions and introduces no such limits. Nothing here attributes #19831 or #19768 to this mechanism or claims an incident-scale memory slope.
Audits Browser Closed Result Retention README (`docs/audits/browser-closed-result-retention/README.md`)
Closed browser dispatcher retains completed results behind pending native work
Before the fix, completed command results stayed in a closed browser dispatcher until its final handler settled. The fix releases settled cache records at close and drops newly settled records while closed. Pending native handlers, page authority, and executor teardown keep their existing lifetime.
Actual paths and bounds
Source references in this section describe the hash-fenced baseline. BrowserClientHostCommandDispatcher.dispatch refuses every command once closed, before authority or duplicate lookup (browser-client-host-command-dispatcher.ts:77–79). close aborts active work and cancels queued work, but retains its pages and cached completed records when the join returns false (:156–179). finishHandler clears those owners only after the last native handler settles (:268–272). A newly settled cancellation record is also cached while a sibling remains active (:297–302).
BrowserClientHostCommandResultCache.clear drops only its record-to-page index. PageState.records and PageState.sequencesByCommandId also own the records; clearing just that index does not release result graphs. Existing releasePage uses exact settled-record eviction to remove both indexes (browser-client-host-command-result-cache.ts:27–55).
Defaults (browser-client-host-command-state.ts:7–13) are 256 pages, 256 active commands, 8 concurrent handlers, 32 queued/page, 64 cached results/page, 1,024 cached results total, and a 5,000 ms close/retirement join. Automation result schema allows at most 768 KiB of JSON-serialized value (browser-client-automation-protocol.ts:5,89–99,116–131). These are count/serialized-value limits, not a guaranteed heap/RSS size. The audit uses 32 tiny results, one native wait, and one canceled tiny queued input; it does not allocate near those maxima.
Production composition calls dispatcher close via PairedRuntimeBrowserClientHost.closeHost (paired-runtime-browser-client-host.ts:165–180). If close times out, actual closeBrowserClientHostComposition defers executor close behind whenHandlersSettled (paired-runtime-browser-client-host-teardown.ts:37–56). Keeping handler/page/native authority alive is intentional. Completed results cannot serve new or duplicate closed requests and need not share that lifetime.
Ordinary handler time boundaries
- The navigation command checks cancellation before starting, then calls
routeWebContents.navigateGuest(browser-client-page-command-execution.ts:20–40). The actual registry delegates tonavigateBrowserRouteGuest, which awaits nativeguest.loadURL(browser-route-guest-lifecycle.ts:99–123) without adding a JS deadline or taking the AbortSignal. Native completion/rejection remains its settlement owner. - Automation checks cancellation before execution, registers the exact guest, and forwards the signal into RPC (
browser-client-page-automation-runtime.ts:42–57; startupmain-process-ready-runtime.ts:61–77). Core handlers such as browser.snapshot destructure runtime and call its method without observing that signal (runtime/rpc/methods/browser-core.ts:39–43). - The ordinary agent-browser helper execution has a 90-second default subprocess timeout (
agent-browser-bridge-types.ts:6;agent-browser-bridge-raw-process.ts:20–36), with overrides for some operations. The agent-bridge embedded goto wrapper separately has a 30-second navigation timeout. Those deadlines are not a universal bound on all handler phases, and the direct route navigate path above does not use that wrapper.
The condition is a handler that outlives the dispatcher's five-second join. No affected-host occurrence, natural indefinite stall, or incident attribution has been established.
Bounded actual-source proof
scenario.test.mjs uses the actual dispatcher, page executor, automation runtime, browser.snapshot RPC descriptor, native-navigation wrapper, and composition teardown function. Existing page-executor harness supplies renderer/session/route ports. The runtime's browserSnapshot/native loadURL are small controlled ports; no Electron window, OS child, real web request, or network is used. Logger/mock result arrays do not own the produced payloads: the automation output and dispatcher handler are plain functions, and each completed value is observed only through WeakRef after the helper returns.
Both Node 26.6 and Electron 43.7 / Node 24.21 pass all 4 cases before and after the two-file fix. A 15 ms join override keeps the proof bounded; the native port is explicitly resolved in finally and all native custody eventually settles.
| Observation | Before | Fixed |
|---|---|---|
| Completed small payload objects alive after timed-out close | 32 | 0 |
| Cached records after close (32 results + create + queued cancellation) | 34 | 0 |
| Canceled queued input still reachable | yes | no |
| Running native handlers after close | 1 | 1 |
| Page/executor and route/session custody retained | yes | yes |
| Close repeated before native settlement | false | false |
| whenClosed pending before native settlement | yes | yes |
| Payload objects alive after explicit native settlement | 0 | 0 |
| First late cancellation cached while sibling remains pending | 1 | 0 |
Open request replay preserves the exact same Promise and runs once. Closed duplicates fail with dispatcher_closed. Normal native resolution and rejection both complete the existing settlement path. Executor close and route/session release happen only after native settlement in both variants.
The initial candidate compatibility run passed 78 tests in 5 files, including its three lifecycle cases and existing dispatcher, page executor, paired-runtime composition, and paired-runtime host tests. The permanent retention suite adds two object-lifetime regressions: releasing completed results while native navigation stays pending, and releasing one late closed record while a sibling handler remains active. Final validation passed 77 tests across 5 production suites, Node typechecking, and all five explicit quality scans over product and artifact code. Reversing the patch produces the two expected lifetime failures while all 16 existing dispatcher tests pass. Commands and outcomes are recorded in validation.json.
Fix scope
fix.patch changes only:
src/main/browser/browser-client-host-command-dispatcher.ts: release each page's already-settled cache during close, after cancellation; discard newly settled records instead of caching them when closed.src/main/browser/browser-client-host-command-result-cache.ts: accept an optionalretainflag inrecord, defaulting to the existing caching behavior. When false, existing identity-checked eviction drops the settled record before any cache admission.
Active/cancelling records, pages, native promises, abort behavior, join timing, FIFO, generation/authority checks, and the closed-settlement promise remain owned exactly as before. No row/byte cap changes. The host and executor continue waiting for their existing settlement owners.
retirePage is separate
selectCommandPage rejects retiring and retired generations before findExistingCommand (browser-client-host-command-page.ts:34–43). Thus retired duplicate replay is already unavailable, even though the cache remains until forget/replacement. The control confirms that behavior and verifies explicit forget releases the cache and preserves the stale-generation floor. This fix leaves that existing retirement policy unchanged; freeing results on page retirement is a separate possible follow-up, especially while executor cleanup is still pending. Do not assume a live duplicate replay contract where the actual admission path rejects first.
Reproduction and source fences
ORCA_BACKGROUND_LAUNCH=1 ORCA_BROWSER_CACHE_VARIANT=before pnpm exec vitest run --config docs/audits/browser-closed-result-retention/vitest.config.mjs docs/audits/browser-closed-result-retention/scenario.test.mjs
ORCA_BACKGROUND_LAUNCH=1 ORCA_BROWSER_CACHE_VARIANT=fixed pnpm exec vitest run --config docs/audits/browser-closed-result-retention/vitest.config.mjs docs/audits/browser-closed-result-retention/scenario.test.mjsFor Electron run the installed binary with ELECTRON_RUN_AS_NODE=1 and ORCA_BACKGROUND_LAUNCH=1, passing node_modules/vitest/vitest.mjs and the same arguments. Reports are separate per runtime/variant; set ORCA_BROWSER_CACHE_OUTPUT to another file path to preserve captured reports. sources.cjs reverses fix.patch in memory and checks exact baseline/fixed hashes plus 21 caller/dependency hashes. The config loads those sources at their real production module IDs without changing checkout files. A synthetic CRLF control checks all 24 source/patch reads against canonical LF hashes. Both variants use the same controlled producer and lifecycle ports.
source-versions.json records 23 canonical-LF source/caller hashes. All 23 match named main checkpoint 291b4ddd6f1c1af480169885e0fda7f9c78ff053; 21 match v1.4.198. Both fixed source baselines match both named versions. The proof executes current source/dependencies, not a historical application binary.
Permanent regression validation
ORCA_BACKGROUND_LAUNCH=1 pnpm exec vitest run --config config/vitest.config.ts src/main/browser/browser-client-host-command-retention.test.ts src/main/browser/browser-client-host-command-dispatcher.test.ts src/main/browser/browser-client-page-command-executor.test.ts src/main/browser/paired-runtime-browser-client-host-composition.test.ts src/main/browser/paired-runtime-browser-client-host.test.ts
ORCA_BACKGROUND_LAUNCH=1 ORCA_BROWSER_CACHE_VARIANT=before pnpm exec vitest run --config docs/audits/browser-closed-result-retention/vitest.config.mjs src/main/browser/browser-client-host-command-retention.test.ts src/main/browser/browser-client-host-command-dispatcher.test.ts
ORCA_BACKGROUND_LAUNCH=1 pnpm tc:nodeThe baseline regression command intentionally fails the two new lifetime assertions. The source and object counts prove a code mechanism, not incident-specific browser use, native stall duration, aggregate app RSS, or attribution to #19831.
Audits Browser Destroyed Guest Retention README (`docs/audits/browser-destroyed-guest-retention/README.md`)
Destroyed browser guests retain main-process callbacks
An embedded browser guest's destroyed event called cleanupGuestPolicyAttachment. That removed its primary page-to-WebContents lookup but left four per-page cleanup callbacks that capture the destroyed WebContents wrapper, plus renderer/workspace/worktree/profile metadata. unregisterAll subsequently iterated only the now-empty primary lookup: three callback maps and the renderer/workspace metadata survived that cleanup too.
Renderer reload can destroy guests without each page sending explicit unregister IPC. A later close of a restored, unmounted page does not send that IPC either: destroyPersistentWebview returns early when its renderer registry has no guest. Explicit unregister correctly releases these resources; same-page re-registration also replaces its callbacks. The defect affects destroyed owners that do not take either path.
The fix routes destruction through the existing unregisterGuest with a guest-retirement reason only when that exact guest still owns the primary page ID. Already bound downloads retain their existing renderer routing until they settle; explicit page close still cancels them. Unregistered guests and popups retain policy-only cleanup. A stale callback cannot unregister a replacement. Normal renderer-process loss keeps its live WebContents and metadata for reload recovery; a fresh guest registration supplies its ownership metadata again. Shared browser sessions and sibling pages are untouched.
Download lifetime correction
Review found that the initial fix treated guest destruction as logical page closure and canceled bound downloads. An exact-source before/after control confirmed that difference with an EventEmitter guest and controlled DownloadItem. Guest retirement now releases guest-owned callbacks while preserving ongoing page downloads, their destinations and cancel authorization. A retained numeric renderer route drains after the last download settles, provided no replacement guest, other download or newer routing owner needs it.
Nine additional controls cover progress and completion/error delivery, explicit close after guest destruction, multiple downloads, replacement guests/routing, repeated guest destruction and renderer loss. Together with four existing browser suites, 55 tests pass; Node typecheck and ordinary/anti-slop lint pass. These controls do not establish native Chromium download survival after destruction on each operating system. No download capacity or wire format changes.
Reproduce
With dependencies already installed, run from the repository root:
ORCA_BACKGROUND_LAUNCH=1 node docs/audits/browser-destroyed-guest-retention/reproduce.mjsThe script runs the actual manager and guest callback installers with EventEmitter WebContents fixtures. It removes only the destruction guard in memory for the baseline, then runs the same nine tests on the fixed source. A temporary test observer records actual map sizes before each assertion. It launches no Orca window or browser process, limits each worker to 512 MiB and each run to 60 seconds, uses the shared process launcher, and removes temporary files. Results include source hashes and runtime provenance.
| After 1,000 distinct guest destructions | Before | Fixed |
|---|---|---|
| Primary guest lookup | 0 | 0 |
| Each context-menu / grab-shortcut / app-shortcut / wheel cleanup map | 1,000 | 0 |
| Each renderer / workspace / worktree / profile map | 1,000 | 0 |
| Policy cleanup map | 0 | 0 |
Each context-menu / grab-shortcut / app-shortcut map after unregisterAll |
1,000 | 0 |
Renderer and workspace maps after unregisterAll |
1,000 | 0 |
Baseline: six tests pass, three fail. Fixed: all nine pass. Controls cover explicit unregister, same-ID replacement with a captured old callback, popup and pre-registration destruction, renderer-process recovery, fresh guest registration, and two pages sharing one browser session profile. The selected existing browser-manager and offscreen lifecycle suites also passed: 64 tests across seven files including the new suite.
Version and limits
Targeted source reads of v1.4.198 confirm the same destroyed-event policy-only cleanup, map ownership, and unregisterAll omission. The executable comparison uses current production source; it does not launch the historical app. This is a retaining path present in the version reported by #19831/#19768. It does not establish that either incident followed this destruction sequence, or measure native memory retained by a destroyed WebContents. The 1,000 iterations measure retained callbacks and metadata, not 1,000 surviving Chromium processes or a gigabyte allocation.
Adjacent audit negatives: explicit page close removes the renderer guest registry and main registration; worktree switching deliberately parks guests under the existing hidden-worktree retention policy; offscreen creation is synchronously indexed with shutdown admission and exact-window teardown; client-hosted async page creation checks availability after acquisitions and cleans canceled owners. PDF capture rejects late disconnected-client completion, its stream buffers have a five-minute TTL, and existing screenshot commands have deadlines. No additional native screenshot hang or unbounded native-page acquisition was reproduced. The separate late renderer registration reply can restore small page-ID metadata after close; it is outside this main-process fix.
Audits Browser Registration Reply Retention README (`docs/audits/browser-registration-reply-retention/README.md`)
Late browser registration replies restore retired renderer state
createBrowserPageWebviewGuestSession awaited registerGuest IPC and then wrote the returned guest ID into the renderer's persistent registeredWebContentsIds map. An explicit close could remove the webview and map entry before that reply arrived; a delayed success restored the retired entry. An older reply could also overwrite the ID of a replacement guest. Its follow-on callbacks could synchronize an obsolete annotation bridge or mutate recovery state after the listener session was disposed. Separately, recovery validation could issue repair IPC after its initial registration query outlived that owner.
The fix checks the existing recovery disposal state, current listener ref, persistent registry identity, and captured WebContents ID before accepting a reply or running those continuations. It makes no new registry and sends no late unregister IPC. A current hidden guest still accepts successful registration. When a persistent guest remounts, the new session's existing validateAfterResume path retries registration if the old reply was ignored. Current unsuccessful replies and current repair retain their prior behavior.
Reproduce
With dependencies already installed:
ORCA_BACKGROUND_LAUNCH=1 node docs/audits/browser-registration-reply-retention/reproduce.mjsThis runs the actual renderer session, recovery controller, and persistent guest registry against headless DOM fixtures and deferred IPC replies. The baseline reverses only the included production patch in memory. A temporary observer records map/callback counts after all replies settle. The script uses the shared process launcher, 512 MiB workers, a 60-second deadline, and temporary files removed in finally. No Orca window, native guest, or remote host is launched.
| After 1,000 explicit guest closes and delayed successful replies | Before | Fixed |
|---|---|---|
| Live webviews | 0 | 0 |
| Retained registration entries | 1,000 | 0 |
| Late annotation synchronizations | 1,000 | 0 |
| Unregister calls | 1,000 | 1,000 |
The baseline fails ten tests and passes six controls; fixed source passes all 16. Cases cover distinct closed IDs, replacement elements, a changed guest ID on the same element, a remount reusing the same element/ref, disposed and moved refs, registry removal before listener disposal, a throwing identity getter, hidden current guests, successful/inconclusive replies, and late versus current repair. The repair-completion case verifies that an old success cannot clear a newer guest's recovery error. All host-guest suites also pass: 196 tests across 23 files, including recovery, viewport, registry, worktree retention, and paintability. The web typecheck passes.
Version and limits
Targeted reads of v1.4.198 confirm the same unconditional registration setter, post-reply callbacks, post-query repair, and close-time map deletion. This establishes a renderer retaining path in the reported version, not that #19831 or #19768 exercised it. Each retained entry is a page ID and numeric guest ID. This proof does not show a surviving native browser process or explain gigabyte-scale memory growth. The independent main-process destroyed-guest callback retention has its own fix and proof.
The registration reply is the only production setter of registeredWebContentsIds; explicit close and replacement remove its key. Following callers found no second setter that could recreate this same metadata after removal. The annotation callback uses current page routing, which is why skipping a stale callback is necessary without issuing cleanup against a replacement.
Audits Browser Viewport Owner Retention README (`docs/audits/browser-viewport-owner-retention/README.md`)
Retired browser viewport operation ownership
The viewport operation captures a guest ID, then awaits CDP commands. Closing a tab deletes its viewport state, but the old continuation can subsequently recreate the UA-intent entry. A failed UA clear can also restore the old value over a replacement guest's completed desktop preset, or a late clear can delete the replacement's mobile intent.
The correction reuses that captured guest ID at three mutation boundaries: before publishing an applied preset's UA intent, before reading/deleting a cleared preset's intent, and before failed-clear rollback. Same-owner rollback, native UA profiles, navigation behavior, and the per-tab promise chain are preserved.
Evidence
The regression fixture calls the actual manager, registration, unregistration, and viewport implementation. Electron WebContents and pending debugger replies are controlled ports; no native browser or window is launched.
- Baseline: 7 failing ownership regressions, 5 passing controls.
- Fixed: 12/12 ownership cases, plus 30 existing viewport, navigation, partial-failure, and UA cases.
- Sixteen pending UA-clear rejections after
unregisterAllleave 16 retired UA entries before, zero after. Registration, preset, and promise maps remain empty. - Other regressions cover closed-tab late success, failed-clear rollback, mobile/desktop replacement, and native-to-default profile replacement.
- Controls preserve ordinary serialized mobile/desktop/null operations, both native-profile presets, same-owner rollback, and the replacement promise tail while old queued operations settle.
- An independent reviewer ran all 12 candidate cases and reviewed the three mutation guards before promotion.
The retained entries are tab ID strings and booleans. This does not demonstrate retained native WebContents, a process RSS slope, or gigabyte-scale memory growth. In-flight CDP work still owns its continuation until it settles. Positive and negative post-close command replies are injected schedules, not an affected-host trace.
Ordinary callers and compatibility
The renderer requests overrides when the user selects a viewport preset and on guest dom-ready, including null presets. The trusted IPC handler validates dimensions before calling this manager. Navigation later reads the UA-intent map, so stale replacement values can alter the standing mobile/desktop identity. The fixture does not execute the renderer or IPC producer.
Both local webview and host-side offscreen registrations use these maps. The correction changes no wire fields, protocol, execution-host ownership, native process lifecycle, folder/worktree handling, or UI layout. It only prevents an operation for a different guest from mutating the current registration's state.
source-versions.json records 11 paths at audit checkpoint 4a09b1d1, independent main 291b4ddd, and reported v1.4.198 e0826956. The viewport implementation, registration, registry declarations, IPC handler, and toolbar producer match all three. Ten sources match independent main and eight match v1.4.198. The guest-session producer contains an earlier audit fix; historical navigation and fixture sources differ. This is a current-dependency replay with the exact historical viewport source, not a historical app-binary replay.
The browsing activity in #19831 makes this path applicable in principle. The report does not establish the required overlap or tab count, and this small metadata mechanism does not account for its reported memory totals.
Reproduction
From the worktree, run the fixed regression suite:
ORCA_BACKGROUND_LAUNCH=1 node node_modules/vitest/vitest.mjs run --config docs/audits/browser-viewport-owner-retention/vitest.config.mjsRun the same tests with the exact baseline viewport implementation; exit status 1 and seven failed cases are expected:
ORCA_BACKGROUND_LAUNCH=1 ORCA_VIEWPORT_BASELINE=1 node node_modules/vitest/vitest.mjs run --config docs/audits/browser-viewport-owner-retention/vitest.config.mjsThe import overlay never rewrites product files. baseline-source.txt contains only the original viewport module; current support modules remain in use. baseline-results.json, fixed-results.json, and validation.json record the measured results and their scope.
Audits Cdp Stream Retention README (`docs/audits/cdp-stream-retention/README.md`)
CDP outbound retention reproduction
From the worktree root:
ORCA_BACKGROUND_LAUNCH=1 node docs/audits/cdp-stream-retention/reproduce.mjsThis bundles the current response writer and connects real loopback WebSockets. The reader pauses while the producer sends at most 128 MiB. The script starts no Electron application or PTY and closes all sockets. before.json records the same experiment against the unbounded writer; after.json records the fixed writer.
Before the fix, 128 MiB of generated payload left 133,177,280 bytes in the WebSocket buffer, with roughly linear growth at every sample. The fixed writer uses the existing outbound queue: an 8 MiB socket soft threshold, 64 MiB held-queue cap and 4,096 queued-frame cap. The stalled connection terminates on overflow and releases its queue. The socket threshold can overshoot by one frame; a single large reply on a clear connection remains permitted, preserving large PDF/screenshot responses. This bounds accumulated backlog, not the allocation needed to construct an individual response or the aggregate across arbitrarily many browser pages.
Unit tests also cover a reader that drains a complete burst in order, request/session correlation, queue disposal on close/replacement, and a 65 MiB healthy reply. Debugger events and command responses share this writer.
This establishes an Electron-main retention mechanism when a CDP automation client stops reading. Neither #19831 nor #19768 establishes that precondition, so it is not a confirmed cause of either incident. RSS reflects GC/allocator timing; queued bytes are the direct measurement.
Audits Claude Branch Proof Streaming README (`docs/audits/claude-branch-proof-streaming/README.md`)
Claude transcript branch-proof allocation
Orca verifies the complete conversation ancestry before preserving or resuming a Claude session. This change reads the transcript incrementally instead of keeping the whole file and its split lines in memory. It still checks every record in the observed byte range, including old ancestors and disconnected conflicts. It does not just parse the first line.
Reproduce
After the repository's normal dependency installation:
ORCA_BACKGROUND_LAUNCH=1 node --expose-gc docs/audits/claude-branch-proof-streaming/reproduce.cjs /tmp/claude-branch-streaming-results.json
ORCA_BACKGROUND_LAUNCH=1 node docs/audits/claude-branch-proof-streaming/regressions.mjs /tmp/claude-branch-streaming-regressions.jsondiff is a pinned direct development dependency. The harness reverses fix.patch against the checked-out reader in memory and verifies the original reader's SHA-256 before building the comparison. It does not modify production files. esbuild writes temporary modules, which are removed along with their require-cache entries on exit. No window or network access is needed.
The variants in the JSON are:
baseline: the original full-file reader, reconstructed and hash-checked.candidate: an open-ended streaming experiment retained as a control. It can admit newly appended records and is not the implementation being merged.windowCandidate: the exact checked-out production reader, including its one internal refresh after descriptor growth.
results.json and electron-results.json were regenerated against production revision 2e623a227474101160de4c977bfae69da4b052ee. They record the runtime, tool versions, harness inputs, actual bundled dependencies, production source variants, and bundle hashes. Allocation and duration values vary between runs. provenance.json now describes the shipped source; reproduction fails if its source hashes or quoted call-site lines drift.
The obsolete initial-window and open-ended result snapshots were removed because they described a different splitter and reader. Their experimental results remain available in git history. baseline-initial-results.json is explicitly historical: its original runtime and complete dependency graph were not captured, so it is not current reproducibility evidence.
Production behavior
The reader opens the file once, stats that descriptor, and streams a finite range through the existing line splitter. Each message body can be released after parsing; the UUID ancestry graph remains until validation finishes. The existing 10,000-ancestor limit stays unchanged. No transcript quota, record-size cap, eviction, or tail-only proof is introduced.
A valid first range returns immediately, even if a newer marker appears later. When the first range is missing a marker/cursor or ends in an incomplete record, the reader checks the same descriptor's size. If the descriptor size increased, it retries the entire proof once over the enlarged finite range. Already-completed repairs therefore succeed without a caller retry. An unfinished repair can still require the existing caller retry. Conflicting ancestry, invalid sessions, malformed middle records, and append-order errors remain errors.
An increased size does not establish append-only writes or prove that the original prefix stayed unchanged. This is not an atomic filesystem snapshot: in-place overwrites and truncation can affect bytes inside the range, as with the old reader. Failed restat, shrink, and pathname replacement do not prove growth. The original opened descriptor is always closed before the production reader returns or throws.
Reachability
provenance.json records exact source hashes and call sites for the current implementation and reported release v1.4.198:
- The local structured Claude runtime adapter resolves the account's transcript and delegates to the full branch proof through
session-file-resolver.ts. - Closing a structured session reads the transcript after connection close, before persisting the handle. A proof failure preserves the previously observed leaf. Unexpected-exit persistence uses the same reader.
- The resumed-PTY verifier calls the full reader and retries incomplete-tail errors on its existing 100 ms cadence, within a 15-second deadline. Static absence and invalid history remain fatal there.
These paths already used unrestricted full-file reads in v1.4.198. Rewind handling is newer than that release. The audit establishes source reachability and synthetic allocation reduction; it does not establish a user's transcript size, the number of concurrent readers, or the cause of a reported production OOM.
Evidence and limits
Both recorded runtime audits pass 51 static real-file cases across all file/string APIs, 24 growth and cleanup controls, and 10 actual-verifier cases. Growth controls inject append, replace, delete, shrink, stat-error, and read-error events against real files. The three completed-repair verifier cases now succeed on the first caller attempt. Static absence, conflicts, session mismatches, and append-order failures retain their error behavior.
The harness checks the original FileHandle for closure. Historical readers without an exposed handle also check descriptor identity, avoiding false leak reports when the OS reuses a closed descriptor number. The fixture uses a private Node filesystem binding only for deterministic timing; production uses public APIs.
regression-results.json records 68 resolver, rewind, recovery, and history-window tests passing on each of the three variants (204 tests). The focused production run also passed all 26 streaming tests: 94 tests across the five relevant files in total. Tests were run on macOS arm64; this does not claim a Windows/Linux or live SSH integration run.
The 32 MiB allocation fixture uses 64 message bodies of 512 KiB with realistic identifiers. The current Electron audit measures about 34.6 MB additional sampled live heap for the original reader and 1.1 MB for the production reader. Both release almost all of that allocation after returning. Sampling forces GC at actual JSON.parse boundaries; it is not RSS, a natural peak, or evidence of a retained leak. GC noise can make retained deltas negative.
A single 8 MiB record still needs about 16.8 MB under every variant. The identifier graph still grows with record count. This removes whole-transcript payload retention; it is not a total memory cap.
The separate duration control reads one stable 2.79 MB file with 8,193 small records, using two warm rounds and five measured rounds in alternating order without forced GC during timing. Electron medians were 4.9 ms before and 7.37 ms after. These are synthetic per-run observations, not a throughput guarantee or a timing gate.
electron-results.json uses installed Electron 43.7.0 / Node 24.21.0 in Node-only mode, with no app/window launch:
ELECTRON_RUN_AS_NODE=1 ORCA_BACKGROUND_LAUNCH=1 node_modules/electron/dist/Electron.app/Contents/MacOS/Electron --expose-gc docs/audits/claude-branch-proof-streaming/reproduce.cjs /tmp/claude-branch-streaming-electron.jsonOther platforms use their installed Electron executable with the same environment and arguments. This is compatibility evidence for the installed runtime, not the historical Electron binary.
Audits Claude History Window Budget README (`docs/audits/claude-history-window-budget/README.md`)
Claude history-window read quota
Restart reconciliation's Claude history reader declares a 16 MiB source quota, but the original stat followed by unrestricted readFile admits later growth. Reuse readNodeFileWithinLimit to enforce the same quota while reading from one descriptor. Overflow preserves the existing inconsistent-history result; it cannot establish that a submitted message was absent.
Reproduce
ORCA_BACKGROUND_LAUNCH=1 node docs/audits/claude-history-window-budget/reproduce.mjsThe seven-case fixture calls the actual history reader, branch proof and bounded reader against real temporary files. It injects an append immediately after the relevant size snapshot. The baseline reverses only fix.patch in a temporary Vite transform, leaving the checkout unchanged. Each child has a 45-second timeout and a 512 MiB old-space ceiling. No application window or provider process starts.
| Case | Before | After |
|---|---|---|
| 305-byte valid source grows to 17 MiB after size snapshot | Reads 17,825,792 bytes and accepts the history | Stops after 16,777,217 bytes, rejects history and closes its descriptor |
| Stable valid source | Accepted | Accepted |
| Exactly 16 MiB | Accepted | Accepted |
| Initially 16 MiB plus one byte | Rejected before content read | Same |
| Growth within quota | Reads the additional bytes and accepts | Same |
| Read error | Inconsistent history | Same; descriptor closed |
| Missing anchor | No source read | Same |
Before: one failing fixed-behavior assertion and six passing controls. After: seven passing cases. results.json records observed read bytes, size snapshots, descriptor counts, source hashes, exit codes and timeout status. These are read-budget measurements, not RSS or retained-heap measurements; the byte quota does not describe all parser allocations.
This reader is absent from v1.4.198, so the finding cannot explain #19768 or #19831. It fixes a current-source quota race. The separately unrestricted proveClaudeTranscriptBranch file reader has no declared byte quota and is outside this change. The related legacy-import quota race is already addressed by #20976.
Audits Claude Task Retention README (`docs/audits/claude-task-retention/README.md`)
Retained Claude background-task text
The actual Claude task tracker retained oversized input strings through its
512-character description/name slices. Its live tasks, settled tasks, and
recently removed tasks can each retain those slices. The fix uses the existingownRetainedString at the shared text boundary; normalization, UTF-16 clipping,
task identity, publication, and lifecycle behavior stay the same.
This extends ML-018 / #20960.
Reproduce
ORCA_BACKGROUND_LAUNCH=1 node --expose-gc docs/audits/claude-task-retention/reproduce.cjsThe script bundles the actual tracker and its retention classes. Its baseline
removes only the new copy call in memory. It exercises flat strings, concatenated
strings, and JSON-parsed SDK-style frames; each input has a distinct task owner.
It measures after GC, then clears the tracker and yields before measuring cleanup.
Results and bundle hashes preserve the complete run.
| JSON-parsed case | Input per task | Tasks | Visible text | Heap before | Heap after |
|---|---|---|---|---|---|
| Live | 64 Ki characters | 32 | 16,384 characters | 2,125,672 | 43,536 |
| Settled | 64 Ki characters | 32 | 16,384 characters | 2,127,072 | 44,296 |
| Removed, awaiting outcome | 64 Ki characters | 32 | 0 characters | 2,108,136 | 25,360 |
| Live | 4 Mi characters | 8 | 4,096 characters | 33,562,624 | 11,048 |
| Settled | 4 Mi characters | 8 | 4,096 characters | 33,563,960 | 11,656 |
| Removed, awaiting outcome | 4 Mi characters | 8 | 0 characters | 33,558,584 | 7,008 |
Captured with Node v26.6.0 on macOS. Cleanup returned near the initial heap for
every case. Six regression tests retain the actual tracker through these three
lifetimes for both descriptions and names. Text behavior tests preserve whitespace
normalization, fallback names, and a clipped surrogate pair.
Reachability and limits
claude-stream-json-connection.ts forwards SDK messages to the structured adapter,
whose emit calls backgroundTasks.observe. Installed SDK 0.3.251 uses Nodereadline to assemble stdout records, parses each record with JSON.parse, then
yields it. The inspected path imposes no record or description length limit;
native read-chunk size does not cap an assembled JSON field. Descriptions are
declared as plain strings in SDKTaskStartedMessage.
The description slice and this SDK version also exist in v1.4.198; that tag
keeps the reader inline in claude-background-task-tracker.ts. The separate
settled/recently-removed retention and name-reader paths describe current code.
The current maps are count-bounded: at most 256 live, 256 settled, and 256 recently
removed entries per tracker. Settled context clears when no visible work remains;
recently removed context awaits an outcome, eviction, or explicit clearing.
Session end/close clears the tracker. Copy work is at most 512 UTF-16 code units
per retained field, and it does not reduce temporary parsing allocation.
These are synthetic oversized task fields, not evidence that an affected user
received such fields. The path concerns structured Claude sessions, not ordinary
terminal output or stderr. Neither #19831 nor #19768 establishes this trigger.
The separate digest-bounded subagent ID was also checked at actual consumers.
The mobile response sanitizer can temporarily retain the original until JSON
serialization flattens its concatenated ID. Worker transcript bounding already
serializes for its byte budget and released that parent in the probe. No durable
ID-owner leak was established, so that helper is unchanged.
Audits Closed Editor Model Lifetime README (`docs/audits/closed-editor-model-lifetime/README.md`)
Closed editor model lifetime
Closing the final editor, or closing a file while every editor panel is hidden, can leave its Monaco text model registered for the renderer's remaining lifetime. The old cleanup hook lived in the conditionally mounted EditorPanel: it disappeared before observing the final close and missed closes while absent. MonacoEditor uses keepCurrentModel, so unmounting the widget does not provide the missing cleanup.
The fix subscribes from the app shell and lazily receives the model registry from the existing monaco-setup load. It retires only closed tab owners. It preserves live same-URI editors, replacement model objects, generated diff namespaces, attached models until detachment, and caches written by a reopened owner during a disposal callback. Existing scroll, selection, preview, and PDF caches retain their separate 20-entry LRU lifetime. No open tab, dirty draft, or live editor history is pruned. Closed-file undo follows Monaco's existing bounded policy; it is not unlimited (see below).
Measured result
| Actual source control | Before | Fixed |
|---|---|---|
| Eight distinct last-file closes | 8 registered closed models; 2,097,208 logical characters; 0 open files | 0 registered models; 0 open files |
| Close with another editor still mounted | Closed model disposed; sibling preserved | Same |
| Close while hidden, then open another editor | Old closed model remains; 2 models for 1 open file | Old model disposed; 1 model for 1 open file |
| Normal terminal/editor switch | Original open model preserved | Same |
All four controls pass with their stated before/fixed expectations on both graphs and both runtimes: 32 comparative cases, Node 24.20.0 and Electron 43.7.0 / Node 24.21.0. Reports are worktree-*-results.json and main-*-results.json. Contents use eight distinct fixture paths and 256 KiB of logical characters per model; this is a controlled amplification, not a field file-size measurement or heap-byte estimate.
This explains a concrete renderer retention mechanism relevant to #12845. The same conditional panel, hook placement, and keepCurrentModel chain exists in reported v1.4.170; source-versions.json records four historical source hashes and matching line numbers. That comparison is static, not an execution of the historical application. It does not establish which allocations caused #12845's reported heap, or the process and cause of #19831.
Ownership and regression coverage
The 27 permanent cases across five suites in closed-editor-model-{lifetime,reentrancy,registry,shell}.test.* and closed-editor-view-state-retention.test.ts use actual installed Monaco models and store close actions. They cover final/hidden panel closure, live siblings and shared URIs, replacement model identities, attachment/detachment, reopening, registry removal/replacement, reentrant registry changes, diff prefixes, bounded caches, and ordinary edit closes without a global registry scan. Together with the three existing disposal/cache suites, 47 tests pass. The fixture exercises Monaco's real attachment ports without creating a native editor widget.
The startup routing suite now also guards the production app-shell hook placement: deleting that call fails the test. All 75 selected tests across nine suites pass. Full Web typechecking currently reports one unrelated existing unused NativeChatMessage import in NativeChatMessageList.windowing.test.tsx; it reports no errors in the changed files.
Registry notifications cancel obsolete queued callbacks but preserve captured model identities and attached-model listeners. A temporary missing registry defers cleanup until registration returns; an interrupted disposal batch is retried. The identity fence prevents that retry from disposing a successor at the same URI. Controller teardown still cancels that controller's ownership and listeners; this audit does not claim to reconcile all preexisting models after an app-shell HMR remount.
Closed-file undo decision
Keep Monaco's bounded closed-file undo store rather than retaining every edited model indefinitely, following the same design used by VS Code's model service. Installed Monaco keeps about 20 MiB of closed-file undo data. Tests confirm ordinary POSIX paths and file:///C:/... paths restore undo when reopened with matching contents. Large files (over 10 MiB of text in the fixture) and raw Windows C:/... paths do not. Those cases lose undo after close/reopen, including final/hidden tabs whose models previously leaked. Existing view-state caches remain intact within their 20-entry budget. This is a deliberate bounded-memory tradeoff, not zero user-visible risk.
Source fences and independent main publication
source-versions.json fences 1,021 repository paths: the complete loaded repository graph of the comparative proof plus selected static wiring/caller files. Vite rejects any additional repository module under src/ or config/. Package code is not part of that repository-graph claim: twelve separate dependency hashes pin the installed package metadata and relevant Monaco model/registry/event and Zustand store implementations.
- Worktree baseline:
f142544a8653eedab2eec0237a9005001497b2f5. - Publication main:
291b4ddd6f1c1af480169885e0fda7f9c78ff053. fix.patchcontains only the eight product changes, including two new modules. The loader reverses it from the checked-out fixed sources to reconstruct baseline behavior.main-context.patchreconstructs 22 unrelated context differences, including two paths absent on main. It is proof scaffolding; do not apply it as a product patch.- Main retains its unrelated
registerShellMarkdownAliasesimport/call inmonaco-setup.ts. The standalone product patch preserves both lines. - The loader checks each before/fixed hash, refuses source/dependency drift, normalizes CRLF to LF, and supports both the working checkout and an independently published main checkout.
loader-results.jsonrecords synthetic CRLF and simulated-main-checkout controls for all four graph/variant combinations.
The comparative test executes the actual legacy surface, old/new cleanup hook, disposal functions, store close action and model registry. It supplies controlled Panel/Shell ports and a fixture store to the hook. Actual app-shell placement, first lazy registration, conditional panel callers and keepCurrentModel wiring are source-fenced static checks; it does not render the entire application or exercise HMR through Vite itself. It opens no Electron window, starts no native editor widget, and measures no RSS, heap bytes, or incident allocation rate.
Review follow-up
The registry-transition regression is covered by four new cases. The uninformative cold-registry fixture assertion was removed, and this audit's product patch, source hashes, loader controls, and all eight comparative reports were regenerated from the current sources. The audit directory is now explicitly allowed in .gitignore.
Reproduce
From the repository root with its installed dependencies:
ORCA_BACKGROUND_LAUNCH=1 pnpm exec vitest run --config docs/audits/closed-editor-model-lifetime/vitest.config.mjs
node docs/audits/closed-editor-model-lifetime/loader-controls.cjsThe default is ORCA_CLOSED_MODEL_GRAPH=worktree and ORCA_CLOSED_MODEL_VARIANT=fixed. Repeat with each graph (worktree, main) and variant (before, fixed). Set these environment variables using the syntax of your shell; the runner and path handling are platform-neutral. Set ORCA_CLOSED_MODEL_OUTPUT or ORCA_CLOSED_MODEL_LOADER_OUTPUT to an output path to avoid replacing checked-in reports.
For Electron, run the installed Electron binary with ELECTRON_RUN_AS_NODE=1, ORCA_BACKGROUND_LAUNCH=1, --no-experimental-webstorage --expose-gc, followed by node_modules/vitest/vitest.mjs run --config docs/audits/closed-editor-model-lifetime/vitest.config.mjs and the same graph/variant environment. This uses Electron's Node runtime without creating a window.
Audits Codex Prompt Claim Retention README (`docs/audits/codex-prompt-claim-retention/README.md`)
Codex prompt claims retained after turn completion
Confirmed cancellation keeps a prompt claim until its turn completes. If the prompt's lookup entries are evicted or replaced first, the old clearTurn() cannot find it. The separate claims map then retains the prompt until the whole session is cleared.
The fix includes claimed prompts in the existing exact-thread/turn cleanup. Existing turn matching and forget() object-identity checks preserve a replacement prompt's authority. Registry limits and cancellation timing are unchanged.
Source ownership and reachability
codex-structured-provider-events.ts:57registers incoming prompt requests and publishes them through the translator.codex-structured-session-acquire.ts:95binds the translator's turn cleanup to the session registry.codex-structured-prompt-ownership.ts:33acquires the claim. Confirmed cancellation deliberately leaves it owned; unsuccessful/unconfirmed cancellation releases it. The actualCodexStructuredTurnCancellationinvokes the confirmation callback after the injected interrupt transport acknowledges success.codex-prompt-registry.ts:258trims the address and journal-binding maps independently. Neither trim removes claims. Replacing the same journal address can similarly leave the old claim without a lookup entry.- A later
turn/completedgoes throughtranslateCodexNotification, the journal translator andsettleCodexJournalTurn. Accepted lifecycle settlement invokesclearPromptTurnatcodex-structured-journal-settlement.ts:170. - The old cleanup enumerates only address/binding values. The fix also enumerates
claims.keys(), still filtering by the exact thread/turn.forget()deletes replacement lookup entries only when they contain that same prompt object.
The safely expired owner is the claim for the terminal turn whose cleanup has been admitted. Live claims survive unrelated turn cleanup and registry eviction. A refused lifecycle settlement does not clear them.
Reproduce
From the worktree root, using installed dependencies:
ORCA_BACKGROUND_LAUNCH=1 node --expose-gc --max-old-space-size=192 docs/audits/codex-prompt-claim-retention/reproduce.cjsFor Electron, use its installed executable with ELECTRON_RUN_AS_NODE=1, the same flags and script. The final optional argument selects the report path; the default is node-results.json beside the script. No Electron window is created.
sources.cjs reverses fix.patch against current source and rejects a baseline hash mismatch. It neither reads a previous commit to reconstruct the implementation nor changes product files. The proof bundles actual source in memory. Each report records effective source and bundle hashes, dependency hashes and runtime versions. Only the requested report is written.
The fixture uses the actual registry, server-request delivery, cancellation ownership function, cancellation class, journal translator and delayed notification delivery. It injects an accepting journal sink, interrupt transport and primary-turn lookup. Prompts belong to child threads, so the production child-turn cancellation branch does not enumerate or terminate processes. Every injected process helper throws if unexpectedly called.
The sequence creates 32 ordinary small prompt objects, confirms their cancellations, admits 256 unrelated prompts to evict lookup entries, then completes the original exact turns. WeakRefs count prompt liveness after forced collections. No large payload is attached. The 20-second deadline and 192 MiB heap limit bound the proof.
Results
Both Node 26.6.0 and Electron 43.7.0 / Node 24.21.0 produced:
| Observation | Before | After |
|---|---|---|
| Retained cancelled prompts after lookup eviction, before completion | 32 | 32 |
| Retained after exact turn completion | 32 | 0 |
| Retained after all lookup maps become empty | 32 | 0 |
| Retained after session clear | 0 | 0 |
| Old prompt retained after same-address replacement and old-turn completion | 1 | 0 |
Ordinary completion releases its prompt on both versions. Wrong-thread, wrong-turn and refused-completion controls preserve claims. The replacement prompt and its active claim remain valid after old-turn cleanup on both versions.
The four regression tests cover 32 evicted claims, replacement authority, a compatibility turn digest and session cleanup. Applying the reversed source produces three expected failures; the session-clear control passes. Existing prompt ownership/reply tests also pass on the reversed source. Current source passes 71 tests across six files plus Node, CLI and Web typechecks; validation.json records commands and other checks.
Limits
This is a code-level lifetime defect. The request/completion ordering is deliberately injected; this is not a capture of Codex emitting that sequence or an affected host. Counts do not measure ordinary prompt bytes or establish a growth rate. It does not identify the cause of #19831 or any other incident.
source-versions.json records matching baseline source at the named main revision. No historical application runtime was reproduced. The fix uses existing turn ownership and identity checks; it adds no arbitrary eviction policy.
Audits Crashpad Read Limit README (`docs/audits/crashpad-read-limit/README.md`)
Crashpad bounded reads
After a crash, Orca extracts a short diagnostic signature from a potentially large dump.
The old reader loaded the complete dump. The current parser reads bounded ranges through
four retained 64 KiB pages and scans embedded diagnostic text in roughly 1 MiB windows.
It can capture a report that grows beyond the existing 64 MiB directory-discovery limit
without keeping that report in one large buffer. Reports already beyond that limit when
discovered retain the existing exclusion policy.
Reproduction
ORCA_BACKGROUND_LAUNCH=1 node docs/audits/crashpad-read-limit/reproduce.mjsUse the repository's installed dependencies and supported Node version. The runner checks
committed SHA-256 values in source-hashes.json before executing any tests. This includes
the capture/parser, test fixtures, runner and its local imports, plus package and lockfile
versions. It refuses changed sources instead of silently recording new hashes. Updating
the manifest requires reviewing the changed evidence and rerunning the proof.
The runner executes the actual capture and file-source tests with synthetic files, then
repeats them with only the zero-size extent deadline disabled through an in-memory Vite
transform. The mutation changes no files. This is a regression control for the deadline,
not a simulation of every behavior in the original implementation.
| Phase | Pass | Fail | Skip | Exit |
|---|---|---|---|---|
| Deadline removed | 24 | 2 | 0 | 1 |
| Current implementation | 26 | 0 | 0 | 0 |
The two expected failures verify that a file reported as size zero stops being read at
the deadline, after either one or two pages, even though more bytes remain available.
The controls fail both on the observed extent and the number of actual file reads.
The runner checks their exact names and exits unsuccessfully for unexpected results.results.json records the verified hashes, counts, failed cases, exit codes and timeouts.
Other controls cover 80 MiB sparse dumps, metadata beyond 64 MiB, marker/check-message
boundaries, growth and replacement during capture, zero-size growth, truncation, descriptor
cleanup and partial-header retry behavior. A symlink swap, deleted candidate, or directory replacement now skips only that candidate; another valid dump can still supply the signature. The symlink-swap test is skipped on platforms without O_NOFOLLOW. The largest requested parser read is 1 MiB +
4,096 bytes, plus four retained 64 KiB metadata pages. A 1,042-module fixture requires no
more than eight reads, preventing repeated reads between module and name pages.
Parser compatibility
ORCA_BACKGROUND_LAUNCH=1 node docs/audits/crashpad-read-limit/stream-signature-parity.cjsThe separate parity script compares 47 fixtures with the published parser at09dbe227547fadaec8d9163f35fd127b0dc1c3ed, using both in-memory buffers and real file handles.
It covers annotations, modules, exceptions, chunk boundaries and marker exhaustion. It
also prints five warm timings for 8/64 MiB sparse dumps; these local synthetic timings
are not a platform-wide performance guarantee. This older parity script does not enforce
the source manifest; use the manifest-checking regression runner first.
Limits and user impact
This removes a potentially large transient allocation after a crash; it does not establish
the cause of normal-session OOMs or an aggregate process-memory cap. Independent captures
can overlap. No evidence ties this file-growth race to #19831 or #19768.
The opened file supplies the parsed ranges. In-place rewrites are not an atomic snapshot.
Nonempty files retain their opened extent. A file opened at size zero receives at least one
read, then observes growth only until the capture deadline; later bytes may therefore be
omitted from that diagnostic signature. This keeps a continuously growing report from
holding capture open indefinitely. Dump files themselves are not truncated by this reader.
Crashpad normally writes an invalid header first and promotes a completed file later:
database implementation
and minidump writer.
The fixtures represent that ordering; they do not reproduce a field incident or a native
Crashpad process. All checks run in background Node processes without app windows.
Audits Daemon Late Exit README (`docs/audits/daemon-late-exit/README.md`)
Delayed daemon output after a synthetic exit
A daemon stop response can arrive on its control socket before its final DATA and
EXIT events arrive on the separate stream socket. Main then emits a synthetic exit.
The delayed DATA recreates a headless model and marks the runtime PTY connected;
the old duplicate-exit check suppresses the physical EXIT before runtime cleanup.
The host has no live session, but main retains the connected record, title tracker
and headless terminal.
Reproduce
From the checkout, using its installed dependencies:
ORCA_BACKGROUND_LAUNCH=1 node docs/audits/daemon-late-exit/reproduce.mjs /tmp/daemon-late-exit-results.json
ORCA_BACKGROUND_LAUNCH=1 node node_modules/vitest/vitest.mjs run --config config/vitest.config.ts src/main/ipc/pty/daemon-late-exit.test.tsThe script uses the real daemon server, client, provider, socket pair, kill IPC
handler, listener binding, and runtime. The native subprocess boundary is a fixture
whose force-kill callback reports exit. Pausing only the stream reader makes the
independent control/stream ordering deterministic. No renderer or visible app is
launched. Temporary Vitest files are removed afterward.
The before case moves duplicate suppression back ahead of runtime cleanup in the
loaded module only. It retains the current incarnation-aware marker representation,
which does not affect the same-incarnation race. The on-disk source stays unchanged.
The script verifies its transform boundary and records the current source hash.
Results
results.json contains four before/after controls:
| Scenario | Before | After |
|---|---|---|
| Kill reply overtakes queued DATA and EXIT | Connected; headless model and title tracker retained | Disconnected; both released |
| Host inventory verifies exit before queued DATA and EXIT | Connected despite an exited verdict; models retained |
Disconnected; models released |
| Kill reply overtakes EXIT with no queued DATA | Disconnected; cause remains stop_unverified |
Disconnected; confirmed requested stop |
| Natural DATA then EXIT | Disconnected; models released | Same |
All eight runs receive one physical provider exit, deliver all final output to the
renderer admission boundary, send one renderer exit, and call the runtime exit
listener once. Fresh daemon inventory is empty in all cases. Additional regression
tests cover same-ID replacement, stale provider callbacks, legacy unstamped exits,
and one-time dispatch settlement with the real SQLite orchestration database.
The fix always processes the current incarnation's provider exit in main. Duplicate
suppression applies only to the renderer notification. When the provider supplies an
incarnation, its marker names that stopped incarnation and cannot suppress a
differently stamped replacement's exit. Legacy unstamped events retain their
existing matching behavior. A matching marker restores the original stop intent
while normal exit-cause resolution still handles negative,
unconfirmed exits. No output is dropped and no wire fields or opcodes change.
Report correlation and limits
The early-return listener and synthetic renderer-kill exit are present in bothv1.4.197 (#19018) and v1.4.192 (#17344). The reproduced connected: true plusstop_unverified state matches #19018's reported contradiction and provides a
concrete main-process retaining path relevant to #19831. This does not prove which
ordering occurred in either user's session, explain #19018's failed subsequent
inventory/close reconciliation, or by itself prove persisted tab resurrection in
#17344. A missing diagnostics.memory row is not process-exit evidence; this proof
uses the owning daemon's physical exit and fresh session inventory.
A second runtime cleanup may advance an already-retired surface's topology revision
once more. It does not republish a removed surface. Existing exit listeners and
waiters remove themselves on settlement; completed dispatches are no longer active.
The existing marker timeout remains 30 seconds. The separate asynchronous shutdown
call's ownership across its await is outside this change.
Audits Daemon Shared Owner Incarnation Retention README (`docs/audits/daemon-shared-owner-incarnation-retention/README.md`)
Shared daemon owner incarnation retention
A degraded daemon provider creates two owner resolvers with one shared route map. On an authenticated daemon identity change, the attach resolver removes that daemon’s routes first. The liveness resolver then sees no corresponding routes and previously left its private session-to-incarnation entries behind. Repeating replacements with newly discovered session IDs grows that private map for the lifetime of the degraded provider.
The fix removes private incarnation entries whose shared route is absent after provider invalidation. It preserves every remaining route, including another provider’s live session and a same-ID successor. It does not change process liveness, stop remote work, change the wire protocol, or depend on a git workspace.
Actual ownership and trigger
src/main/daemon/daemon-provider-init.ts:123selectsDegradedDaemonPtyProviderfordegraded-new-pty-fallback; startup discovery runs at line 139.src/main/daemon/degraded-daemon-owner-recovery.ts:15constructs both resolvers with the same map; public discovery and liveness probes populate their private indexes. Startup reconciliation can also record both routes.src/main/daemon/degraded-daemon-owner-recovery.ts:70subscribes to each daemon’s identity publication and invalidates the attach resolver before the liveness resolver.src/main/daemon/daemon-pty-connection-lifecycle.ts:41publishes only after a different authenticated identity replaces a previous identity. Repeated observation of the same identity does not retire anything.src/main/daemon/daemon-pty-daemon-recovery.ts:268can replace the daemon while retaining its adapter and the degraded provider.src/main/daemon/daemon-session-owner-resolution.ts:44performs the invalidation and the new private-metadata prune.
This is a local desktop main-process degraded-provider path. Loss of SSH contact is not its retirement trigger. Entry counts below do not establish retained bytes, RSS, an OOM, or causation for #19831.
Bounded actual-source proof
The fixture uses the actual degraded provider, recovery controller, resolvers, daemon adapter inventory, authenticated identity publication, and direct attach implementation. Only finite authenticated transport replies and the empty fallback provider are inert; it starts no native PTY, socket, network connection, or application window. It does not depend on garbage-collection timing or a never-settling promise.
Each of 32 cycles discovers a new current-daemon session, populates both resolvers through public calls, observes an unchanged identity, then publishes a replacement identity. An unrelated legacy-daemon session remains live throughout. Finally, an ordinary legacy exit removes its route from both resolvers.
| After 32 replacements and the legacy exit | Baseline | Fixed |
|---|---|---|
| Shared routes | 0 | 0 |
| Attach resolver incarnation entries | 0 | 0 |
| Liveness resolver incarnation entries | 32 | 0 |
Additional assertions preserve a same-ID successor on another provider, an unchanged authenticated identity, direct attach with a matching authoritative incarnation without inventory, refusal of a mismatched authoritative incarnation, and ordinary exit cleanup. The permanent tests include the repetition regression and three compatibility controls; the portable fixture additionally exercises the actual adapter attach transport path.
Reproduce
Run from the repository root with its dependencies installed:
ORCA_BACKGROUND_LAUNCH=1 node docs/audits/daemon-shared-owner-incarnation-retention/reproduce.cjs
ORCA_BACKGROUND_LAUNCH=1 pnpm exec vitest run --config config/vitest.config.ts src/main/daemon/daemon-shared-owner-incarnation-retention.test.ts src/main/daemon/daemon-session-owner-resolution.test.ts src/main/daemon/degraded-daemon-pty-provider.test.tsThe runner accepts an optional output filename as its first argument. On macOS, the Electron runtime control is:
ORCA_BACKGROUND_LAUNCH=1 ELECTRON_RUN_AS_NODE=1 node_modules/electron/dist/Electron.app/Contents/MacOS/Electron docs/audits/daemon-shared-owner-incarnation-retention/reproduce.cjsOn Linux or Windows, use the corresponding installed Electron binary with the same environment variables. It runs as Node and never displays a window.
The baseline test overlay reverses only the fenced product patch in memory:
ORCA_BACKGROUND_LAUNCH=1 pnpm exec vitest run --config docs/audits/daemon-shared-owner-incarnation-retention/before.config.mjs src/main/daemon/daemon-shared-owner-incarnation-retention.test.ts src/main/daemon/daemon-session-owner-resolution.test.ts src/main/daemon/degraded-daemon-pty-provider.test.tsExpected: exactly the new repeated-retirement assertion fails before the fix; the other 53 tests pass. All 54 pass with the fix.
Source identities and publication independence
sources.cjs checks the exact fixed source hash, reverses fix.patch, checks the baseline hash, and fences every evaluated TypeScript dependency. It records actual evaluated and input hashes and a bundle hash in each report. A CRLF control checks source and patch normalization. The default runner needs neither Git history nor ignored audit notes.
source-versions.json records the audited source graph (276 modules) and the independent main graph at 291b4ddd6f1c1af480169885e0fda7f9c78ff053 (274 modules). Both graphs are accepted explicitly; ten surrounding modules differ because of unrelated audit fixes. The proof therefore does not require those fixes to be stacked. The publication reports were produced through the exported run({ readSource, output, sourceLabel }) API, reading each non-target source from that named main revision and applying only this product change. The default command also runs directly on that publication tree with the fix and artifact installed.
Node 26.6.0 and Electron 43.7.0 / Node 24.21.0 both produced the table above against both source graphs. All four executions used the working installation’s external packages. These are source overlays, not historical application or dependency installations.
At reported v1.4.198 (e0826956fcfc532f5a1e55b5e081f2e57e553c43), the resolver, shared recovery controller, and authenticated identity publication match the recorded baseline exactly. The surrounding degraded provider differs, as recorded in historicalCore; no whole-v1.4.198 execution or incident attribution is claimed.
validation.json records tests, typecheck, full-file artifact quality, and limits. The four result files contain measured entry counts and exact source/artifact identities.
Audits Daemon Stream Retention README (`docs/audits/daemon-stream-retention/README.md`)
Daemon stream retention reproduction
Run from the worktree root after installing dependencies:
ORCA_BACKGROUND_LAUNCH=1 node docs/audits/daemon-stream-retention/reproduce.mjsThe script bundles the current DaemonStreamDataBatcher with esbuild, then uses real loopback TCP sockets. It pauses the reader, feeds at most 8 MiB, honors the producer-pause callback, resumes the reader, and closes every socket in finally. It starts no PTY, agent, or application window. Output includes the bundle hash, queued payload sizes, RSS, and drain outcome.
Visible 64 KiB and 1 KiB producers should pause within the backlog budget and resume after both queues drain. A hidden 1 KiB producer should keep running while the existing keep-tail policy bounds its held output. RSS includes allocator/GC timing; queue measurements are the direct evidence.
after.json records a historical fixed run under Node v26.6.0: the visible bulk producer paused after 2,686,976 characters with 131,300 socket bytes and 2,031,616 held characters; the visible small-write producer paused with 3,401,200 socket bytes. The hidden producer processed all 8 MiB with 131,300 socket bytes and 667,648 held characters. Every case resumed and drained both queues to zero.
Preserved measurements before the fix
before-1mib-chunks.json and before-64kib-chunks.json were captured before adding producer backpressure, with a real paused reader and 96 MiB of input. The latter includes the original compiled bundle hash and a hidden-session comparison.
| Input | Socket buffered bytes | Held characters |
|---|---|---|
| 32 MiB | 131,321 | 32,636,928 |
| 64 MiB | 32,809,583 | 33,554,432 |
| 96 MiB | 66,406,511 | 33,554,432 |
The last 32 MiB retained another 33,596,928 queued payload bytes. The held queue plateaued at 32 MiB while the socket queue grew at 1.00127 bytes per produced ASCII byte. Resuming the reader drained both queues in 74 ms. Sampled RSS reached 1.38 GB with 64 KiB chunks; this includes string coalescing/slicing and GC effects.
The original measurement used Node v26.6.0 on macOS; the current reproduction records its runtime and should be run on the supported Node 24 toolchain. The historical build command was:
ORCA_BACKGROUND_LAUNCH=1 node docs/audits/daemon-stream-retention/reproduce.mjsThis proves a current stalled-consumer retaining path. It does not attribute issue #19831's whole-system memory total to this path; that report lacks per-process measurements.
Audits Empty Streamed Delta Retention README (`docs/audits/empty-streamed-delta-retention/README.md`)
Empty streamed deltas retain array entries
The text coalescer charged streamed text by UTF-8 bytes but appended an array entry for every empty delta. A live stream receiving repeated empty updates could retain an increasing number of entries while both byte counters stayed zero. Flushing published a joined string and kept the entries. The actual Codex notification path accepts delta: ''; this diagnostic exercises its stream handler and coalescer.
The fix skips only the empty chunks.push operation. Empty-stream creation, snapshots, dirty state, scheduled publication, callback receiver, backpressure and eviction remain unchanged.
Reproduce
ORCA_BACKGROUND_LAUNCH=1 node docs/audits/empty-streamed-delta-retention/reproduce.cjsThe runner reverses hash-checked patches in memory and checks every bundled source dependency. It changes no product files and starts no native process or UI. A bounded CRLF control checks source and patch loading. Reports were recorded on Node 26.6.0 and Electron 43.7.0's Node 24.21.0.
| Control | Before | Fixed |
|---|---|---|
| Four batches of 16,384 empty Codex deltas, flushing each batch | 16,384 → 65,536 retained slots | 0 slots after every batch |
| Logical stream count | 1 | 1 |
| Accounted / observed text bytes | 0 / 0 | 0 / 0 |
| Scheduled callbacks / published rows in the complete caller scenario | 6 / 5 | 6 / 5 |
Append hé after empty updates |
Same 3-byte text | Same 3-byte text |
| Forget and disposal | Clear retained state | Clear retained state |
The runner compares the entire recorded publication and scheduling behavior before/after. Controls also cover first-empty snapshots, failed publication and retry, rejection of a new empty key while the previous stream is backpressured, accepted eviction, callback receiver, UTF-8 truncation and an empty update after truncation. The two runtimes each execute four source phases: current/main before and fixed, plus the v1.4.198 coalescer before and with the same narrow guard.
The permanent regression invokes the actual Codex stream caller. A temporary Array.prototype.join observer measures the matching chunk array only during synchronous snapshot creation, then restores the method. The baseline fails with 65,537 slots versus the expected single nonempty prefix; the other 14 coalescer controls pass. All 64 focused compatibility tests pass with the fix. See validation.json.
Source and incident scope
The current baseline is byte-identical to the coalescer at named main commit 291b4ddd6f1c1af480169885e0fda7f9c78ff053. The exact v1.4.198 coalescer contains the same unconditional empty append; its surrounding implementation differs. Historical phases replace only that module and use the recorded current Codex caller/dependencies. This is a source overlay, not a packaged historical application replay. source-versions.json records these distinctions and named caller hashes.
Claude's generic checkpoint API also uses the coalescer, but its ordinary provider path rejects empty text in claude-streamed-block-identity.ts before calling it. This artifact demonstrates the Codex path and preserves Claude compatibility; it does not claim an ordinary Claude trigger.
Measurement instrumentation reads private map and array cardinalities without changing their contents. These are retained-entry counts, not heap, RSS or byte measurements. The fixture keeps the live stream owned until forget/disposal; it does not establish retention after all owners collect. Nonempty one-byte deltas can still have substantial array overhead within the text-byte allowance, and overflow concatenation has its own transient cost.
No affected-host data establishes how often Codex emitted empty updates in #19831 or another incident. The finding is a reproducible code-level growth mechanism present in the reported release. It does not attribute an app-scope OOM total to this mechanism or establish its incident magnitude. No remote protocol, process liveness, process termination or terminal ownership behavior changes.
Audits Gitlab Known Host Retirement README (`docs/audits/gitlab-known-host-retirement/README.md`)
Retire obsolete GitLab known-host generations
Each successful getGlabKnownHosts probe previously stored a host-list array under a connection ID plus its SSH provider generation. Reconnecting under the same ID created a new entry while every earlier successful generation remained cached until an explicit preflight reset. The cache now keeps one successful generation per observed execution identity.
Async publication also uses the existing coalescer's ownsKey() and checks the current SSH generation. A result completing after reconnect, explicit reset, or replacement by a newer probe cannot recreate retired cache state. Original callers can still receive their own completed result. Explicitly remembered hosts, native/WSL separation, command routing and existing probe timeouts are preserved.
Evidence
The runner bundles the actual cache, coalescer and parser. Only command-result and SSH-generation ports are controlled; it opens no SSH connection and runs no GitLab command. It reverses fix.patch in memory, verifies the original source hash, and compares that baseline against the unmodified current product source. Reports include product, dependency, regression-test and fixture hashes.
| Control | Original | Fixed |
|---|---|---|
| Successful result arrays retained after 128 generations | 128 | 1 current array |
| Remembered result arrays retained after 16 generations | 16 | 1 current array |
| Delayed old-generation result after a successor answers | Still retained | Collectable; successor preserved |
| Explicit reset followed by old completion | Old result repopulates cache | Next lookup executes a fresh probe |
| Abandoned probe finishes after its replacement | Old host added to replacement cache | Replacement remains unchanged |
| Explicit reset after retention exercise | 0 original arrays retained | 0 original arrays retained |
Both phases preserve remembered-host updates while probes succeed or fail and isolate native, Ubuntu WSL, Debian WSL and two connection IDs. results.json records Node26.6; electron-results.json records installed Electron43.7 / Node24.21 running without an app window. Both runs pass all controls. This is compatibility evidence, not a historical packaged-binary reproduction.
ORCA_BACKGROUND_LAUNCH=1 node --expose-gc docs/audits/gitlab-known-host-retirement/reproduce.cjs
ORCA_BACKGROUND_LAUNCH=1 node node_modules/vitest/vitest.mjs run --config config/vitest.config.ts src/main/gitlab/gitlab-known-host-retirement.test.ts src/main/gitlab/gitlab-known-host-probe.test.ts src/main/gitlab/gitlab-known-host-probe-wsl-fallback.test.ts src/main/git/coalesced-probe.test.ts src/main/gitlab/client-mr-auth-rate-limit.test.tsFor the installed macOS Electron binary:
ELECTRON_RUN_AS_NODE=1 ORCA_BACKGROUND_LAUNCH=1 node_modules/electron/dist/Electron.app/Contents/MacOS/Electron --expose-gc docs/audits/gitlab-known-host-retirement/reproduce.cjs docs/audits/gitlab-known-host-retirement/electron-results.jsonThe runner is portable; that executable path is macOS-specific. Thirty-two focused tests pass, including eight new retention/lifecycle/scope controls. Running those eight against the original source produces six failures and two passing controls. Node typecheck, focused lint (including artifact type-aware/casting scans), and the changed-code quality gate pass. The original product module and reused coalescer match named main 291b4ddd6f1c1af480169885e0fda7f9c78ff053.
Limits and incident mapping
This removes small metadata retained across SSH generations. Distinct historical execution identities may still keep one entry each until reset; this change does not impose a new cache cap or alter connection/provider lifetime. One generation's host list remains input-sized.
The demonstrated accumulation requires changing SSH provider generations, so it cannot explain #19831's reported all-local session. No affected-host observation ties it to another OOM report. The proof measures reachable result arrays, not RSS or gigabytes of incident memory.
Audits Headless Hydration Retention README (`docs/audits/headless-hydration-retention/README.md`)
Late headless snapshot ownership
Reproduced defect
The runtime can retire a PTY or replace its headless model while a renderer/provider snapshot or emulator seed write is pending. The old completion still updates maps keyed only by PTY ID. After exit cleanup, a successful renderer reply recreates CWD, recent-output and title state; even an empty or rejected reply recreates the hydration done entry. A replaced model can also lose its provider preference or pending hydration status to the old completion.
Provider snapshot validation has two related races. Its late generation check calls an allocating getter, recreating an entry that exit just deleted. Its cleanup can delete a newer capture's live-mode scanner Set after the old Set becomes empty. Provider tail parsing has the same allocating late check after an awaited parse/write.
These paths exist in v1.4.198. They explain a concrete main/runtime retention mechanism under PTY churn, but the frequency and retained size in #19831/#19768 remain unproven. The renderer request already has a 750 ms timeout (src/main/ipc/pty/ipc/serialize-buffer.ts); this fix addresses callbacks writing after their owner retires, not an indefinite renderer wait.
Fix and ownership
The three model-seeding paths compare headlessTerminals.get(ptyId) with the captured state before starting work and after asynchronous boundaries. Completion bookkeeping runs only for that state. Provider capture/tail completion compares the existing generation without allocating, and capture cleanup removes the map entry only while it still owns the same Set.
Admission generation allocation and normal queued live writes keep their existing behavior. Disposal still drains live writes queued before retirement. Current hydration, query replay suppression, CWD/kitty metadata and replacement capture mode tracking remain covered. These are local runtime ownership checks for both local and SSH-backed terminals; they add no process-death inference, remote cancellation or wire change. They do not depend on git worktrees.
Reproduce
The original before/after reproduction belongs to commit 9ed2f743a4dd7aab1d35906c049c11d143a92aaf. Its patch targets the runtime layout at that commit; use that checkout to reproduce the historical result:
ORCA_BACKGROUND_LAUNCH=1 node docs/audits/headless-hydration-retention/reproduce.mjsThe script runs the actual OrcaRuntimeService regression fixtures twice. For the before case, it reverses only the included four-file fix.patch in a temporary Vite transform. It neither rewrites source files nor needs an unpublished commit. The after case uses the checked-out source. Source hashes and individual failing cases are recorded in results.json.
- Before: 16 failed, 6 passed.
- After: 22 passed.
The tests control pending promises to cover retirement before callback admission, during renderer/provider replies, during seed writes and during kitty metadata application. They cover success, null, rejection, same-ID replacement, current-state success, generation preservation and live-mode scanner ownership. Provider-tail checks exercise both normal and visible-screen-only parsing.
Current ownership regression coverage lives together in src/main/runtime/headless-hydration-ownership.test.ts; all 22 original cases remain. Current validation:
ORCA_BACKGROUND_LAUNCH=1 node node_modules/typescript/bin/tsc --noEmit -p config/tsconfig.node.json
ORCA_BACKGROUND_LAUNCH=1 node node_modules/vitest/vitest.mjs run --config config/vitest.config.ts src/main/runtime/orca-runtime.test.ts src/main/runtime/terminal-query-responder.test.ts src/main/runtime/headless-hydration-ownership.test.ts --testNamePattern 'headless|hydrat|WSL|provider cwd|live WSL cwd|query|seed|retire|replacement|capture|renderer'Historical validation at #20996: Node typecheck passed. The selected existing runtime/query checks plus the new cases passed 306 tests, with 1087 unrelated cases skipped by the name filter. All runs were headless and used ORCA_BACKGROUND_LAUNCH=1.
Audits Legacy Import Source Budget README (`docs/audits/legacy-import-source-budget/README.md`)
Legacy import source byte budget
The production importer accepted a transcript larger than its existing 16 MiB
quota when the file grew between stat() and reading it. The fix applies that
same quota to consumed raw bytes and rejects the entire import before replacing
the journal. It does not import a truncated prefix.
Reproduce
From the repository root, with dependencies installed:
ORCA_BACKGROUND_LAUNCH=1 node --max-old-space-size=256 docs/audits/legacy-import-source-budget/reproduce.mjsThe script bundles the actual production importer and decoder twice. The baseline
restores the importer's UTF-8 stream and omits the new consumed-byte quota in
memory; the second run uses the current source. All other dependencies are
identical. Explicit file paths bypass discovery; a stub fails if discovery is
unexpectedly called. No historical checkout or local-only commit is required.
Each run creates one real temporary file containing a valid Claude message.
The controlled stat seam captures its original size, appends spaces until the
file is exactly 17 MiB, then returns the original stat. The production importer
reads the grown file. A journal spy records replacement attempts and its epoch;
no SQLite database or user transcript is touched. Temporary files are removed.
results.json records source hashes, actual consumed bytes, stream closure,
and replacement attempts. Before the fix the importer consumes all 17 MiB,
returns success, and replaces the journal with the decoded prefix. Afterward it
refuses on the first chunk crossing 16 MiB, destroys the stream, and leaves the
prior epoch untouched. Read buffering permits one chunk of overshoot.
Process and version attribution
This importer runs in the host runtime through structured-session adoption,
handoff, transcript catch-up, and journal recovery. On desktop that runtime is
Electron main; a remote runtime imports its own host-local source. The stat-only
quota and unrestricted stream already existed in v1.4.198, release commite0826956fcfc532f5a1e55b5e081f2e57e553c43.
The reproduction establishes a quota bypass, not the cause of #19768 or #19831.
It requires an import and source growth/replacement after the size check;
neither incident provides that evidence. This fix is independent of #20963's
resumable JSONL record limit.
Other whole-document audit results
ai-vault/session-transcript-reader.ts:152routes rewritten JSON documents
through whole-filereadFileandJSON.parse. Gemini JSON, Hermes, Devin,
Cline, Grok metadata, Kimi state, Rovo, and OpenCode file readers lack a source
byte quota. These are allocation peaks; the parse cache retains summaries,
not the full documents.- The production default, including v1.4.198, places those scans in the forked
AI Vault service with a 384 MiB V8 old-space limit. That is not a total RSS
limit.ORCA_AI_VAULT_SERVICE_PROCESS=0instead puts scans in a worker thread
sharing main's PID; its first-prompt fallback runs directly in main. - Existing remote whole-document streaming parsers offer a reuse path, but
local conversion must preserve message-sink delivery for search indexing.
Streaming also needs an explicit policy for an individually oversized JSON
value; arbitrary history truncation was not introduced. - The older
native-chat/transcript-reader.tsandtranscript-read-cache.ts
have whole-history behavior, including a newest-entry cache exemption, but
the current production source has no external call sites for those exports.
Validation: 50 targeted tests passed, including a regression that failed before
the fix, UTF-8 and raw-byte limits, exact-boundary acceptance, and stream cleanup.
Node typecheck, targeted lint, formatting, and diff checks passed.
Audits Local Log Tail Lifetime README (`docs/audits/local-log-tail-lifetime/README.md`)
Local log-tail watchers outliving their renderer
Main can receive a log-tail subscription, await path authorization, and finish installing its native watcher after the requesting renderer has gone away. Previously, the destroyed listener was registered only after authorization. Installed watchers also survived a renderer crash or a new document loaded into the same WebContents. The map retained each watcher and its sender callback; callbacks suppressed notifications to destroyed senders without releasing resources. This handler is present in v1.4.198.
The fix gives each sender one owner using the existing abortWhenRendererGone policy: destruction, renderer process loss, or committed document navigation closes its live watches and invalidates pending authorization. Same-document and canceled navigation preserve the owner. For a reused subscription ID, the latest pending request wins. Each pending subscription has an identity token; old completions and old watcher errors cannot replace or close newer subscriptions. Failed authorization preserves an existing installed watch. The last pending/live release removes all owner listeners.
This is a reproduced native-handle and small metadata leak. Watchers do not retain file-content chunks. It does not establish the input frequency or memory scale in #19768 or #19831.
Reproduce
From the repository root with existing dependencies:
ORCA_BACKGROUND_LAUNCH=1 node docs/audits/local-log-tail-lifetime/reproduce.mjsThe script runs the actual IPC handlers against temporary files, real fs.watch handles, controlled authorization promises, and EventEmitter senders. The existing IPC tests use watcher doubles to deliver an error from a retired watcher. No Electron window, real user log, process inventory, or network request is used. Test cleanup releases all watchers.
The baseline reverses only fix.patch in a temporary Vite transform. Working sources remain unchanged; source hashes and exact failed cases are recorded in results.json. Child test runners use the shared cross-platform process runner.
| Version | Passed | Failed |
|---|---|---|
| Before lifetime fix | 9 | 10 |
| With lifetime fix | 19 | 0 |
The twenty-owner case retained twenty native watcher owners before the fix and zero afterward. The broader cases cover destruction during authorization, active-plus-pending replacement, process loss/navigation, failed replacement, explicit stop, idle listener disposal, superseded success/error, and failed native installation. Ordinary tab cancellation already waited for start before stop; that behavior remains covered by the renderer hook tests.
Additional validation: Node typecheck, direct lint, and the existing renderer-lifetime and local-log-tail hook suites. This endpoint only watches renderer-authorized local logs. SSH/paired-runtime execution ownership and wire schemas do not change; the local editor eligibility check already excludes runtime-environment files. Folder workspaces follow the existing path authorization policy.
Audits Module Organization README (`docs/audits/module-organization/README.md`)
Orca file and folder organization assessment
Yes: clean up the busiest directories in small, domain-focused steps. The repeated prefixes often describe folders that should exist, or existing folders whose organization is incomplete. Keep kebab-case for ordinary filenames. The problem is how many unrelated responsibilities sit beside each other and how hard it is to find their owners.
This is a navigation and maintainability investment. Moving files alone does not make the application faster or establish better architecture. It becomes valuable when a contributor can find a feature, understand its entry points, and make a change without searching several enormous directories.
Start with the seven Linear IPC files and ten Jira IPC files, then Jira attachments, Linear issue context, repository settings and ephemeral VM ownership. Follow the quieter platform and tooling work before the busier Task Page and filesystem folders. Schedule the shared contracts, terminal lifecycle and session runtime work with their owners; those areas have broader compatibility and integration obligations. The execution task tracker defines the full sequence.
This assessment proposes moves; it applies none. The accompanying codebase walkthrough explains the system, and codebase-map.json contains the measured directory inventory, dependency summaries and exact proposed paths. audit-plan.json holds the editable architectural labels and proposals.
The execution task tracker now provides the authoritative PR sequence, starting with quieter areas based on recent development activity. It refines the original phase suggestions below into smaller batches and explicitly removes existing forwarding modules. execution-tracker.json records file actions, checks and merge progress; no barrel exports or old-path shims are allowed.
What the filenames are telling us
Your example is a sound instinct. A prefix can act as a folder name written repeatedly:
Current: src/main/ipc/
linear.ts
linear-issue-handlers.ts
linear-project-handlers.ts
linear-team-handlers.ts
linear-custom-view-handlers.ts
linear-ipc-args.ts
linear.test.ts
Proposed: src/main/ipc/linear/
register-handlers.ts
issue-handlers.ts
project-handlers.ts
team-handlers.ts
custom-view-handlers.ts
ipc-args.ts
register-handlers.test.tsThe proposed registration file still exports the same registration function. The linear:* channel names stay the same. A source-path cleanup should not change the external contract.
However, the backend already has src/main/jira and src/main/linear. There is no reason to combine everything mentioning Jira into one repository-wide folder. Jira UI, authenticated backend access, IPC handlers and pure contracts have different jobs and run in different environments. Group by domain within the appropriate layer:
src/main/jira/ backend client and domain operations
src/main/ipc/jira/ desktop request handlers, proposed
src/main/runtime/rpc/methods/ remote request methods
src/shared/jira/ pure contracts and rules, proposed
src/renderer/src/components/
task-page/jira/ existing provider UIThese are related locations, not necessarily duplicate implementations. The feature trails connect them.
Where the problem is substantial
The following counts are JS/TS files directly in a directory, including its tests and support files. They do not include children. A directory with many colocated tests is less concerning than the same count of production modules, so the JSON also separates those categories.
| Directory | Direct files | Assessment |
|---|---|---|
src/shared |
2,905 | Largest discovery problem; partition contracts and pure rules by domain. |
src/renderer/src/lib |
1,292 | Too many unrelated rules and adapters behind one role-based name; introduce concrete domain children. |
src/main/runtime |
1,251 | Keep runtime composition visible; group owned session, workspace and transport responsibilities. |
src/renderer/src/components/terminal-pane |
1,070 | Already a feature folder; needs smaller responsibilities inside it, not another terminal/ wrapper. |
config/scripts |
987 | Organize tool families by purpose; grouping everything named check-* would mix unrelated owners. |
src/renderer/src/components/settings |
838 | Repository settings are a good first section to group. |
src/renderer/src/components/native-chat |
813 | Review composer, journal presentation and interaction boundaries separately. |
src/renderer/src/components/sidebar |
807 | Review concrete sidebar responsibilities; keep host facts separate from view state. |
src/main/ipc |
797 | Provider families and incomplete filesystem/PTY consolidation are clear candidates. |
src/renderer/src/components/editor |
776 | Extend the existing responsibility-based organization; do not move every editor file at once. |
mobile/src/session |
722 | Separate mobile chat, terminal and session-navigation responsibilities. |
src/renderer/src/components |
619 | Finish the existing Task Page subtree before inventing a new feature hierarchy. |
The complete directory inventory covers the whole tracked repository, including cloud, mobile, native code, tooling and documentation. The proposed work is a selected backlog, not an assertion that every other directory is already well organized.
The strongest findings
Complete the organization that is already there
Task Page already has jira/, linear/, github/ and gitlab/ children, yet many task-page-* and use-task-page-* files remain in the parent components directory. The Task Page entry reaches out to those hooks through parent-relative imports. This is direct evidence of one feature spread across two discovery locations. Route provider-specific files into the existing provider children and keep cross-provider composition at the feature root.
Filesystem and PTY code have the same pattern: existing ipc/filesystem and ipc/pty trees coexist with flat filesystem-*, parcel-watcher-* and pty-* families. Consolidate by the responsibilities already established inside those trees. Filesystem watchers include a separately built process entry, so this is more involved than the provider IPC pilot.
src/shared/linear already groups issue types, workspace types, links, media and agent-access contracts. There are no direct linear-* JS/TS files left in src/shared in this snapshot. Jira's equivalent flat family is a concrete place to follow that precedent.
A feature folder can itself be too flat
browser/, claude/, codex/, terminal-pane/ and mobile session/ already have meaningful names. Wrapping each in another folder does not solve their problem. Useful next boundaries are responsibilities such as browser cookies, structured Claude sessions, the Codex app-server adapter and the mobile chat composer.
There is an existing local example in editor/combined-diff: children include browse-files, load-sections, resolve-changes, review-controls, scroll-viewport and remember-view. Those names help someone find an operation. It is a useful organization model, though the folder layout alone does not prove that its implementation is otherwise ideal.
Keep composition visible and scrutinize excessive fragmentation separately
The root store explicitly composes named Zustand slices, and OrcaRuntime composes host responsibilities. These entry points should remain easy to find. Moving their related files into domain folders does not require hiding the composition behind index.ts re-export files.
The repository also enforces file-size limits: generally 300 counted lines for TS, 400 for TSX, 600 for MJS and 800 for tests, with existing exceptions tracked by a ratchet. That policy encourages splitting large files. It does not tell contributors where the resulting modules belong. Add folder guidance alongside it. The audit does not establish that this rule caused the current directory sizes.
The Task Page entry also chains many staged hooks. Folders will improve discoverability, but whether that composition has too many stages is a separate design review. Do not use a file-move PR to silently rewrite state ownership or behavior.
Naming rules I would adopt
- Create a folder when files describe one feature or responsibility with an identifiable owner. Three or more production modules is a useful review prompt, not an automatic rule.
- Prefer completing an existing folder to creating a parallel one. Keep tests and their fixtures with the code they exercise, preserving special shared fixture locations where needed.
- Remove a prefix when the enclosing path already supplies that exact context and the shorter name stays informative:
jira/attachments/image-cache.tsis clearer than repeatingjira-attachment-everywhere. - Retain meaningful context when ambiguity would hurt search or review.
agent-session/journal-position.tsis more useful thanagent-session/position.ts. A descriptive kebab-case filename is healthy. - Use concrete entries such as
register-handlers.ts,read-context.tsor a named slice file. Avoid newutils,helpers,common, or catch-all folders. Avoid a forest of one-file folders and genericmanager.tsfiles. - Keep a small root for composition and entries. Add another level when distinct responsibilities emerge; do not mechanically convert every hyphen into a path separator.
- Keep imports explicit. A new folder does not require an
index.tsthat re-exports every internal file. A folder provides navigation; an enforced boundary or a real package provides stronger isolation.
The first small proposals shorten redundant prefixes. Most larger proposals retain basenames initially so a reviewer can verify the relocation easily. Shortening those names can follow after the boundary is settled. No global case-style migration is needed.
How to pursue the cleanup
Phase 1 contains independently selectable pilots. Start with one provider IPC family. Linear and Jira are lower-risk choices because the selected files are desktop transport handlers around existing backend domains. Lower risk does not mean no validation is needed. Issue-context, attachments and VM code have more lifecycle or permission concerns and should follow after the pilot establishes the process.
Phase 2 completes existing trees and groups clearer subdomains. Phase 3 handles broadly shared contracts and lifecycle code after ownership review. Priority and risk are independent: shared contracts have high navigation value but also high migration cost.
The dependency evidence makes that sequencing concrete. Each provider IPC proposal has one outside production-classified caller. The shared agent-session scope has 1,085, while Jira's shared scope has 176. The flat persistence scope contains only one production-classified module among 85 files, yet 209 outside production-classified files import that scope. Moving a central entry can create substantial churn even when its neighbors are mostly tests. These counts include type imports and reflect direct callers in this snapshot, not runtime coupling or transitive impact.
The exact scope, file counts, outside callers and destinations for all proposals are in the backlog table and codebase-map.json.proposals. Broad proposals must be split into smaller changes; the JSON is an inventory of candidate ownership, not an executable bulk-move plan.
For each implementation PR:
- Select a concrete responsibility and inspect its incoming callers and outgoing dependencies. Agree its public entry and identify any generated inputs, runtime entries, fixtures or compatibility fences.
- Move production files with their relevant tests. Update imports, source-reading tests, aliases, package exports, build entry tables, scripts and CI references. Search for both full paths and entry basenames; the import graph cannot find every path string.
- Keep exported APIs, channel names, wire formats, stored formats, registration order and lifecycle behavior stable. Keep semantic redesign in a separate review.
- Run the affected tests and typechecks. Use desktop checks for desktop moves, mobile's independent checks for mobile/shared consumers and cloud's checks for cloud work. Run applicable boundary and ratchet checks; path moves must not add baseline exceptions to make CI pass.
- Verify packaged worker/process entries when affected, including Windows/WSL and macOS/Linux behavior. Shared-contract changes need mixed-version client/server coverage; workspace changes need both folder workspaces and git worktrees.
For the first Linear IPC PR, the concrete change is seven file moves plus caller updates. Check its registration composition, the Linear IPC tests and desktop typechecking. The main build-entry table becomes particularly relevant for later watcher and worker-related moves. Existing compatibility import fences and the process-host import boundary should remain intact.
Judge success by whether a contributor can locate and change one responsibility in one obvious subtree, with explicit composition and a reviewable diff. Raw filename length, folder count and a lower sibling count are supporting signals, not success on their own. There is no defensible runtime-speed or engineering-hours savings estimate from this static audit.
Keeping the evidence useful
Run from the repository root with its existing Node and pnpm dependencies installed:
node docs/audits/module-organization/generate-map.mjs
node docs/audits/module-organization/generate-map.mjs --checkThe first command refreshes only codebase-map.json and records the checkout commit as snapshot provenance. The second preserves that recorded commit while recomputing and strictly comparing every measured field, including the source-content hash. Committing the audit or other changes outside the measured inventory therefore does not make an otherwise identical snapshot stale. The recorded commit must have a valid full Git object ID; checking does not require its historical object to be available in a shallow checkout. Both commands read Git-tracked paths with current working-tree contents and exclude this audit's own artifacts. Newly added source files must be tracked before they enter the inventory. The hash covers JS/TS paths and contents, not every repository asset.
The generator reuses the repository's tracked-file inventory, AST module-specifier parser, test classifier and root workspace source resolver. Human judgments live in audit-plan.json; changing those judgments requires review. After moves land, remove or update completed selectors before regeneration: an empty selector deliberately fails rather than silently retaining an obsolete proposal. Update the prose tables when refreshing the data; the generator does not rewrite the report.
The import graph includes type-only imports and recognized literal loaders. It counts direct file dependencies, not calls or runtime loading cost. Computed paths, new URL, filesystem reads, spawn/worker strings, package scripts and CI path references require separate inspection. Unresolved specifiers and parse failures are recorded explicitly. Folder size and repeated-prefix families identify places to inspect; the families overlap and must not be summed.
Audits Native Pty Spawn Env Retention README (`docs/audits/native-pty-spawn-env-retention/README.md`)
Native PTY spawn environment lifetime
The native PTY handle's exit callback captured its complete creation arguments solely to read reportsChildExitStatus. Those arguments include the merged spawn environment. Copying that boolean before registering the callback releases the arguments and environment while the PTY remains live.
This is per-handle retention: the original objects also collect after the handle and native event owner become unreachable. It does not establish retention after every terminal closes, native PTY memory usage, an RSS slope, or the cause of #19831.
Ownership and compatibility
src/main/daemon/pty-subprocess.ts:72–113creates the environment, completes preflight and native spawn, then passes a fresh object literal tocreateDaemonPtySubprocessHandle. This is the sole production call site; the caller never stores or mutates that object afterward.src/main/daemon/pty-subprocess/native-pty-spawn.ts:29–74computesreportsChildExitStatussynchronously from the selected native launch command. Every successful return copies the boolean into its result. It is an immutable spawn fact in this call chain.src/main/daemon/pty-subprocess/subprocess-handle.ts:26–69needs the process, projected foreground metadata, scalar exit-status fact and PATH. Its long-lived exit callback previously retained the whole argument object. The fix changes only that capture. Native spawning, native signal ownership, physical-exit ordering, disposal and output buffering are unchanged.- The environment is a fresh merged object, but many of its string values may already be shared with
process.env. Releasing its reachability does not imply an equivalent reduction in resident bytes. Required PATH remains reachable throughshellPathEnv.
source-versions.json records exact hashes. Main commit 291b4ddd6f1c1af480169885e0fda7f9c78ff053 exactly matches the audited wrapper baseline. Release v1.4.198 (e0826956fcfc532f5a1e55b5e081f2e57e553c43) has the same environment capture and callers, but predates unrelated I/O-failure and exit-listener ordering changes. The two-line patch applies to both named sources. This is a historical source comparison, not a historical packaged-runtime reproduction.
The change stays inside the daemon's execution-host wrapper. It adds no remote wire data or client-side process verdict, and depends on neither a git worktree nor a folder workspace.
Bounded before/after proof
sources.cjs reverses fix.patch in memory and checks the exact baseline and fixed SHA-256 values before bundling either version. It imports the actual foreground tracker and pre-listener queue, and records all effective source dependency hashes and the generated bundle hash. No git refs, copied production implementation, build outputs, credentials or ignored notes are required to rerun it.
Source and patch reads normalize CRLF to LF before reversal and hashing; recorded named-source, dependency and event-emitter hashes use canonical LF. The proof also feeds synthetic CRLF source and patch text into the loader in memory and verifies identical before/after source and hashes, without writing product files. This checks the checkout line-ending case, not a Windows runtime.
The fixture uses the installed node-pty JavaScript event emitter and an inert process port. Native termination imports and process.kill are guarded; no native PTY, subprocess scan, OS signal, socket or window is created. WeakRefs measure one small argument object and one small environment object, with no payload amplification. The deadline is 15 seconds and the heap limit in these commands is 128 MiB.
| Runtime | Live handle before: args / env | Live handle after: args / env | After owner drop, both versions |
|---|---|---|---|
| Node 26.6.0 | 1 / 1 | 0 / 0 | 0 / 0 |
| Electron 43.7.0 / Node 24.21.0 | 1 / 1 | 0 / 0 | 0 / 0 |
Both versions preserve PATH, startup-delivery metadata, raw foreground lookup, pre-listener output and exit replay, normal exit codes, signal causes, unavailable wrapper status, dead-handle signal guards and idempotent disposal. node-results.json and electron-results.json contain the measured results.
From the repository root, run the Node proof:
ORCA_BACKGROUND_LAUNCH=1 node --expose-gc --max-old-space-size=128 docs/audits/native-pty-spawn-env-retention/reproduce.cjsFor Electron, run the installed Electron executable with --expose-gc --max-old-space-size=128 docs/audits/native-pty-spawn-env-retention/reproduce.cjs. Set ELECTRON_RUN_AS_NODE=1 and ORCA_BACKGROUND_LAUNCH=1 in its environment. This keeps Electron in Node mode; it creates no windows.
Regression checks
The new lifetime regression measures collection before native exit, then confirms that the live handle still delivers data and exit. Two more cases preserve both exit-status interpretations after collection. Existing lifecycle, foreground identity/cadence, environment inheritance and I/O-failure cleanup suites cover neighboring contracts.
The fixed six-file run passed 114 tests with four existing platform skips. The reversible baseline overlay ran the new and existing lifecycle suites: one expected lifetime failure, 29 passing controls. To reproduce the overlay without editing product files:
ORCA_BACKGROUND_LAUNCH=1 pnpm exec vitest run --config docs/audits/native-pty-spawn-env-retention/before.config.mjs src/main/daemon/pty-subprocess-env-retention.test.ts src/main/daemon/pty-subprocess-handle-lifecycle.test.tsvalidation.json records the verification commands and outcomes. The pending-creation cancellation audit is separate and is not changed here.
Audits Nested Repo Processed Queue README (`docs/audits/nested-repo-processed-queue/README.md`)
Release completed nested-repository scan records
scanNestedRepos kept every consumed TraversalFolder in its breadth-first queue until the scan finished. Those records retained path segments and inherited parsed ignore rules after their directories had been processed. Releasing each consumed slot and occasionally compacting the empty prefix removes that temporary retention while preserving traversal order.
Run
ORCA_BACKGROUND_LAUNCH=1 node docs/audits/nested-repo-processed-queue/reproduce.cjsThe runner uses the repository's process launcher to start a Node child with forced GC, a 256 MiB old-space limit and a 15-second timeout. It bundles the actual scan and ignore-rule parser. An observational hook records weak references and scalar queue counts. A finite injected filesystem pauses one directory read; no app window, PTY, SSH connection or native watcher starts. The unused local Git detector is a throwing stub, ensuring the injected filesystem owns every probe.
The fixture has 96 branches, each with 64 distinct ignore rules and one child directory. It pauses the penultimate child read, leaving one pending directory. Four event-loop-separated GC rounds precede each observation.
| Observation | Before | Slot-release control | Fixed |
|---|---|---|---|
| Completed child records surviving GC | 94 / 94 | 0 / 94 | 0 / 94 |
| Their inherited rule arrays surviving GC | 94 / 94 | 0 / 94 | 0 / 94 |
| Observed records surviving scan completion | 0 | 0 | 0 |
| Total directories visited | 193 | 193 | 193 |
All variants visit the same directories in exactly the same order and return the same empty result. The baseline reverses only fix.patch in memory. The diagnostic control adds only consumed-slot clearing to that baseline, without compaction; it isolates the retaining path. The fixed variant executes the current queue implementation. results.json includes source hashes, exact queue counts, runtime provenance, process exit and timeout status.
The narrow source regression suite exercises a wider traversal across repeated compaction, including Windows and SSH POSIX path forms, inherited ignore rules, breadth-first result order, maximum depth, repository caps, cancellation and optional timeout behavior. All 37 discovery, queue and scan-rule tests passed, along with Node typechecking and focused lint checks. Existing discovery tests cover local filesystem and symlink behavior.
Reuse and scope
The change follows the consumed-slot release pattern in ws-outbound-backpressure-queue.ts and the amortized prefix compaction pattern in runtime-rpc-call-queue.ts. It introduces no new queue abstraction, traversal policy, RPC field or host boundary.
The baseline source matches v1.4.198; the runner verifies this named-tag comparison after normalizing CRLF line endings to LF for Windows checkout portability. This is not an execution of the historical packaged application.
The IPC route uses this scanner for local and SSH-backed folder selection, with filesystem operations delegated to the selected host. Runtime scan/import routes request a 15-second timeout; IPC forwards options, whose timeout defaults to null. Existing time checks happen between awaited operations and do not cancel a pending read.
This fix releases completed work. It does not cap the active frontier, directory entry arrays, .gitignore size or directory breadth. The original implementation releases its records on scan completion. No heap-byte savings or field-incident attribution is claimed; no affected-host data was used.
Audits Osc133 Carry Retention README (`docs/audits/osc133-carry-retention/README.md`)
Retained OSC 133 incomplete carry
The shared command-lifecycle scanner keeps an incomplete OSC 133 suffix of at
most 4,096 UTF-16 code units. A V8 sliced string can keep the entire preceding
PTY input alive through that small suffix. The correction copies only the final
incomplete carry through existing ownRetainedString; short prefixes, content,
parsing, callbacks, authority and reset behavior are preserved.
This adds the fifteenth retained-text boundary to
#20960, following the
kitty/mouse tails and other
retained text slices. It introduces no wire
change and applies equally to local and SSH/remote terminal bytes reaching the
shared scanner.
Reproduce
ORCA_BACKGROUND_LAUNCH=1 node --expose-gc --max-old-space-size=192 docs/audits/osc133-carry-retention/reproduce.cjsRun the same script with the installed Electron executable, settingELECTRON_RUN_AS_NODE=1 and ORCA_BACKGROUND_LAUNCH=1, with the same Node flags.
No application window, native PTY, or network is created. The runner has a
60-second deadline and accepts an optional output-report path as its first
argument; otherwise it writes Node or
Electron results here.
The portable loader validates all 28 bundled source modules and seven additional
caller/fixture files. Non-evaluated provenance callers accept only the recorded
audited or named-main bytes, and reports identify which was present; evaluated
modules each require one exact fixed hash. It reverses only the new import/copy call in memory through
a zero-context patch, then validates the baseline hash. All
evaluated-source and artifact hashes are recorded. It needs no Git history,
ignored notes, or absolute developer paths. Source/patch reads normalize CRLF;
an in-memory CRLF control checks equivalent before/after strings.
The scanner baseline exactly matches named main291b4ddd6f1c1af480169885e0fda7f9c78ff053 and v1.4.198
(e0826956fcfc532f5a1e55b5e081f2e57e553c43). The copier did not exist inv1.4.198; current helpers and callers are used for both sides of this
experiment. Source provenance records each named
identity/absence separately. This is not a replay of a complete historical app.
Result and controls
Both Node 26.6 and Electron 43.7 / Node 24.21 pass 117 cases. Each runtime
compares the baseline, fixed Buffer copier and fixed Bufferless copier through
the actual scanner, shared title tracker, and daemon background transient-fact
relay. Thirty-two 64 Ki-character inputs retain roughly 2 MiB before the copy;
eight 1 Mi-character inputs retain roughly 8 MiB. Fixed deltas are below the
asserted 1 MiB tolerance, including owner overhead. Completion and reset/exit
release the old parents. Exact GC-sensitive measurements are in the reports;
they are heap deltas, not RSS or exact allocation attribution.
The ordinary sequence bytes come from the fish 4.7.1 capture documented insrc/shared/terminal-mode-2031-final-state.test.ts: A;click_events=1 andC;cmdline_url=npx. That capture contains complete OSC sequences. The large
plain-output prefix and cut before the terminator are synthetic. This does
not claim the original capture had those sizes or boundaries.
Controls preserve BEL/ST completion, split prefixes, C/D callback values,
background disable/re-enable, reset, and every split of a Unicode/NUL/lone-
surrogate fixture. The Bufferless copier is selected while Buffer is absent,
then Buffer is restored before measurement; this exercises the renderer's
actual fallback without launching a renderer. Short ordinary D;0 prefixes,
complete sequences and plain input are negative retention controls. Oversized
unterminated input is separately labelled malformed-protocol stress. V8's
independent last successful RegExp input is reset before both measurements.
Eight permanent tests cover both copier paths, long captured-fish suffixes,
completion, reset and short D;0. With the in-memory baseline overlay, exactly
four long-suffix regressions fail at 33,550,680–33,565,360 retained bytes against
a 2 MiB allowance; the other 41 tests in the four-suite run pass. The fixed run
passes all 45. Wider proof/quality validation is recorded in
validation.json.
Owners and ordinary input bounds
Main creates a per-PTY tracker with onCommandFinished inorca-runtime-get-unpersisted-tracked-title-for-pty.ts; scanner enablement still
respects transient-fact consumer/authority state. Ordinary daemon output frames
delivered to main are sliced to 64 Ki characters indaemon-stream-data-batcher.ts, and ordinary relay output to 16 Ki characters
in src/relay/pty-handler.ts. The 64 Ki cases therefore demonstrate retention
without requiring a multi-megabyte main input; the 1 Mi cases amplify the
mechanism. Replay and transformed output have their own existing limits.
The daemon's BackgroundTransientFactRelay owns one tracker per background
session. daemon-terminal-admission.ts feeds it before output batching, so the
batcher's later slicing is not an input cap on this daemon scanner. Native data
passes through pty-subprocess/subprocess-handle.ts, the session's shell
readiness/startup/recovery path, and its stream client. The inspected local
intake does not impose an independent string-length limit; platform/native
library chunk sizes were not measured here.
Completion/replacement of the incomplete escape, scanner reset, session exit,
background retirement, tracker disposal or owner release drops the old parent.
This is at most the last incomplete-parent cost per live scanner, not a list of
every historical chunk. Multiple readers may share the same input backing
storage; do not add their isolated measurements as independent process totals.
This is a reproduced code-level retention mechanism. It does not establish a
native output pause, normal-session frequency/duration, the trigger in
#19831/#19768, a reported sustained growth rate, or a multi-gigabyte incident's
cause. The change does not reduce original input allocation.
Audits Pending Runtime Pane Close README (`docs/audits/pending-runtime-pane-close/README.md`)
Closing an unbound paired-runtime pane with a captured handle
A restored pane can already hold a scoped remote:<environment>@@<handle> layout binding while remote.attach() waits for terminal.resolvePane. The transport's getPtyId() is still null. An explicit split close therefore passed null to closeWebRuntimeTerminal, removed the layout binding, and destroyed only the viewer. The host terminal stayed connected. This attachment/teardown behavior exists in v1.4.198.
This is a specific retained host-terminal mechanism. The change is stacked on the local/direct-SSH pending-close fix in #21001 and reuses its current-owner query. It does not prove the incident frequency in #15210, Linux Electron-main growth, or #19831's memory slope.
Scope and authority
Only an exact scoped handle whose environment matches the owning workspace's runtime authorizes this fix. Existing retirement planning and the shared current-owner query protect other tabs, sibling aliases, and bound transports. The provider helper captures the pairing revision, performs its existing compatibility check, then checks pairing and current ownership again immediately before dispatch. The second call skips only the check that just completed. It sends the existing terminal.close request for the captured handle.
The actual host fixture verifies that re-registering the same PTY ID with a new incarnation allocates a new handle. A close addressed to the old handle rejects and never invokes the controller's kill operation. Client same-leaf adoption, a replaced transport map, and changed worktree/pairing ownership also suppress the queued request. Ordinary detach remains viewer-only.
Native host PTY hints, legacy handles without an explicit environment, and returned different handles are outside this fix. Current client snapshot registries retain freshness/frame identity rather than a live terminal-row incarnation. Inferring destructive authority from a late native-hint resolution could stop a replacement. The separate read-only native-hint and pending web-activation reproduction remains in notes/paired-pending-split-close; it establishes omitted requests, with no claim that these excluded cases are fixed. No parent-tab close, local fallback, new wire field, or capability is introduced. A request is not confirmation of process death; provider failures retain their existing handling.
Close-confirmation review correction
The public split-close callback now probes the captured scoped handle before authorizing retirement, including while terminal.resolvePane remains pending. Live or unverified pending work opens the existing confirmation dialog. Cancel keeps the host terminal; Confirm rechecks the captured tab, pane, transport, handle, host, and pairing revision. A replacement or a split that became the only pane invalidates the old decision. The host's existing handle-incarnation fence and the compatibility-dispatch checks remain in force.
pending-pane-close-confirmation.test.ts adds public-callback controls for both local/direct-SSH and paired pending panes. The comparative counts below remain the original proof snapshot, which called the post-confirmation executeClosePane callback directly.
Reproduce
Run in the repository root with existing dependencies:
ORCA_BACKGROUND_LAUNCH=1 node docs/audits/pending-runtime-pane-close/reproduce.mjsThe script runs 13 tests using the actual split-close hook and remote transport, plus two tests delivering the close RPC into an actual OrcaRuntimeService with a fake PTY controller. React registration and unrelated presentation callbacks are mocked. No Electron window, host process inventory, or real PTY child is used.
The temporary Vite transform reverses only fix.patch; the baseline includes the IPC fix from #21001. Working sources remain untouched. The script uses the shared cross-platform process runner and records source hashes and exact cases in results.json.
| Version | Passed | Failed |
|---|---|---|
| Before scoped-handle fix | 5 | 10 |
| With scoped-handle fix | 15 | 0 |
The baseline failure count includes new eager-request/compatibility assertions, not ten independent leaks. Additional validation: 255 tests in 21 selected renderer suites, full renderer typecheck, direct lint, and the changed-code quality gate pass. The original 24-case IPC proof still runs after the shared ownership extraction; its committed results remain a snapshot of the published IPC source.
Audits Pending Split Close README (`docs/audits/pending-split-close/README.md`)
Pending split close can omit shell retirement
A restored split pane can be explicitly closed while its IPC spawn/reattach reply is pending. Its transport has no bound PTY ID yet. The old close path deleted the durable leaf binding and destroyed the unbound transport without requesting retirement. The late-result cleanup deliberately preserved reattach and cold-restore replies for remounts, so an existing shell or newly cold-restored shell could remain live with no pane and no kill request.
This is a concrete mechanism matching the missing kill requests in #15210. The relevant close, late-result exclusion, and daemon cold-restore behavior exists in both v1.4.184 and v1.4.198. The report does not establish that this sequence produced its 51 shells. It also does not establish a retained Electron-main heap slope or independently explain #19831.
Ownership and fix
The explicit split-close hook captures its durable requested PTY before removing the leaf binding. Existing tab-retirement planning resolves the local/direct-SSH execution owner. It requests retirement immediately and retains an explicit-close callback for the late same-ID result. The late callback rechecks current store ownership and the current transport map, including a replacement at the same leaf. It makes a second request only when no owner remains.
An eager request alone is insufficient: Electron spawn preflight can still be waiting before the adapter/history lock exists. A kill can return SessionNotFoundError, then the pending spawn creates a new cold-restored shell. The late-result request closes that window. If spawn already owns the adapter's history lock, the ordinary known-ID shutdown waits behind it; the control case preserves that behavior.
Generic detach/destroy keeps its existing behavior. Repeated destroy retains explicit intent, while a different returned reattach identity remains protected. The tab aggregate/row ID is not counted as a separate same-tab owner; it can be the closing leaf's own stale index. Other live tabs use all existing retirement ownership sources; remote alias matching reuses the existing normalized identity.
Paired-runtime handles, runtime-owned native hints, and unresolved owners never fall through to local kill. They are outside this IPC fix. A provider failure is logged by the existing retirement helper; requesting retirement is not confirmation of process death. No host inventory sweep, wire change, global tombstone, or remount-driven shutdown is introduced. A shared owner already present at close keeps its established retirement responsibility; this does not change all pending-fresh/shared-owner races.
Close-confirmation review correction
The original tests called executeClosePane after a close decision. A separate review found that the public handleRequestClosePane callback skipped the running-work check while the transport was still unbound. Retirement now obtains the pending local/direct-SSH identity from the existing retirement plan and runs the existing confirmation flow first. An unverified pending probe asks for confirmation; Cancel preserves the process. Before a delayed decision or confirmation acts, the captured tab generation, pane, transport, binding, and execution owner must still match. A split that became the final pane cannot turn an old confirmation into a whole-tab close.
pending-pane-close-confirmation.test.ts exercises the public callbacks, including live and unverified work, Cancel, confirmed close, completed attachment, ownership replacement, and direct SSH. These are separate regression controls added after the original comparative proof below; they do not change its historical case counts.
Reproduce
From the repository root with the existing dependencies installed:
ORCA_BACKGROUND_LAUNCH=1 node docs/audits/pending-split-close/reproduce.mjsThe script runs the actual close hook, layout binding, pane-close handler, IPC transport, daemon server, and daemon adapter. React registration and unrelated presentation/status callbacks are mocked. Daemon cases use temporary sockets, synthetic history, and the existing fake subprocess fixture; no real shell or Orca window is launched. It cleans its temporary configuration and invokes Vitest through the repository's cross-platform runProcess.
fix.patch is reversed only inside a temporary Vite source transform for the baseline. Working files remain unchanged. New helper/test sources remain present, but the baseline close hook cannot call the helper. Source hashes and exact cases are recorded in results.json:
| Version | Passed | Failed |
|---|---|---|
| Before fix | 10 | 14 |
| With fix | 24 | 0 |
The 24 cases retain the original nine reproduction/control scenarios and add same-leaf/new-map/different-tab adoption, sibling ownership, repeated destroy, provider failure/retry, spawn rejection, returned-ID mismatch, direct SSH, unresolved/paired-runtime routing, and folder workspace coverage. Eight separate ownership-query tests cover legacy/scoped aliases and the existing tab ownership sources. The baseline failures include assertions about the new eager request; they are not 14 independent leaks.
Historical entry points: v1.4.184 TerminalPane.tsx:1163, use-terminal-pane-lifecycle.ts:1368, pty-transport.ts:859, and src/main/daemon/daemon-pty-adapter.ts:750. The current equivalents are use-terminal-pane-close-actions.ts, terminal-pane-pane-closed.ts, ipc-pty-connect.ts, and daemon-pty-spawn-result.ts.
Audits Plugin Uninstall Log Retirement README (`docs/audits/plugin-uninstall-log-retirement/README.md`)
Plugin logs after successful uninstall
Successful uninstall previously removed the plugin and its worker while retaining up to 200 log rows under its key. Repeating this across distinct keys retained every old ring. Old worker callbacks could also append into a same-key reinstall; a late stdout tail could write after worker exit and uninstall.
The fix clears a key only after worker deactivation and filesystem removal both succeed. Worker, panel and event producers capture the current log-entry token before asynchronous work. Clearing the entry invalidates those callbacks without retaining the old row array or adding permanent key tombstones. Installed stopped/crashed history and failed-removal history keep their existing policy.
Removal authority
Removal joins the existing refresh chain and resolves the installed object after earlier refreshes finish. A normal refresh replaces discovery objects, so capturing one before joining that queue would incorrectly reject an authorized uninstall. Fresh command, panel and event work for the selected object is blocked while removal owns it; failure restores admission.
The service rechecks bundled ownership before deactivation. The filesystem remover independently rechecks it inside the existing per-directory mutation queue, before deleting files or lock ownership. This protects a newly bundled revision published while the old worker is stopping. The fixture publishes genuinely different bytes: reinstalling identical bytes preserves the previous immutable provenance and does not establish a bundled successor.
A successful removal retires the exact discovered object before a later refresh can publish a successor. This changes no wire format or execution-host policy. There is no process-death inference, extra queue, broad history cap, or kill policy change.
Evidence
The four-control portable comparison uses actual service, IPC handler, installer, discovery, worker runtime, IPC parsing and stdout framing. Worker fork/exit transport, Store settings access and imported plugin code are controlled ports; real temporary files exercise installation and removal. No native worker process or app window is created.
| Control | Before | Fixed |
|---|---|---|
| Eight distinct successful uninstalls, 205 SDK rows each | 8 keys / 1,600 rows / 8 sampled rows alive | 0 / 0 / 0 |
| Old callbacks held after identical-key reinstall | Old log accepted | Successor log remains |
| Worker exit precedes final stdout end | Retired tail retained | No retired key |
| Installed SDK / malformed raw IPC bounds | 8,192-code-unit SDK limit; 200 rows | Same |
Old fake worker objects remain rooted during the collection control. WeakRefs measure reachability of sampled row objects, not RSS or byte savings. Bounded synthetic callback ordering is not a claim that a native worker produced that ordering in an affected incident.
All 89 tests pass across the 17 permanent regressions and 72 existing compatibility tests. The 17 permanent regression controls additionally cover partial filesystem failure/retry, late panel and event diagnostics, refresh-before-remove, changed revisions, both bundled checks, queued identical reinstall, admission restoration, service disposal and installed crash history. Existing compatibility suites cover the host runtime, controller, panel, installer, integrity/reconciliation and IPC behavior.
Source scope and reproduction
source-versions.json records exact source hashes. The original eight changed product baselines match named main 291b4ddd6f1c1af480169885e0fda7f9c78ff053 and reported v1.4.198 e0826956fcfc532f5a1e55b5e081f2e57e553c43. The comparison uses explicitly fenced current dependencies; it is not a replay of either whole historical checkout. The small dependency-context.patch reconstructs four current dependency differences when running from published main. Tests and fixture modules travel with this change. The new ninth product module, plugin-installation-state.ts, contains the extracted installation/log state and removal transaction. It is absent from both named baselines; the service remains the sole refresh-queue owner. The source inventory supplies 249 before / 250 fixed repository paths (including the optional baseline test), with six package manifests. Actual comparative evaluation loads 246 before / 247 fixed repository paths.
The loader reverses the exact zero-context fix for the before run, verifies every supplied repository source and package-manifest hash, and rejects unfenced runtime repository imports. The loader controls verify CRLF normalization, source drift rejection and a simulated publication checkout. Installed third-party runtime code is represented by the recorded package manifests, not claimed byte-identical to historical packages.
From the repository root:
ORCA_BACKGROUND_LAUNCH=1 ORCA_PLUGIN_LOG_VARIANT=before pnpm exec vitest run --config docs/audits/plugin-uninstall-log-retirement/phase.config.mjs
ORCA_BACKGROUND_LAUNCH=1 ORCA_PLUGIN_LOG_VARIANT=fixed pnpm exec vitest run --config docs/audits/plugin-uninstall-log-retirement/phase.config.mjs
ORCA_BACKGROUND_LAUNCH=1 node docs/audits/plugin-uninstall-log-retirement/loader-controls.cjsFor Electron's installed Node runtime, use the installed Electron executable with ELECTRON_RUN_AS_NODE=1 ORCA_BACKGROUND_LAUNCH=1 and node_modules/vitest/vitest.mjs followed by the same run --config arguments. ORCA_PLUGIN_LOG_OUTPUT and ORCA_PLUGIN_LOG_LOADER_OUTPUT select alternate report paths. before.config.mjs runs the six permanent retention cases against baseline and is expected to fail those retention assertions.
Review correction, 2026-09-17
The first published head failed the child-process import boundary because the mock support filename did not identify it as a test fixture. Renaming it to __mocks__/plugin-uninstall-log-test-fixture.ts uses the guard's existing test-fixture exemption. The native fork mock and real host/SDK IPC lifecycle remain unchanged; no production importer, allowlist or count limit changed.
After the rename, 23 checks passed across the three uninstall suites, the child-process boundary suite and the two native worker supervision cases on macOS. Node typecheck, ordinary lint and anti-slop passed. All 16 before/fixed comparative controls passed again across Node and Electron's Node runtime, and loader controls passed with the renamed source fences. The prior 89-test batch above remains historical evidence for the unchanged product code.
Successful uninstall now discards the previous installation's 200-row in-memory history. The settings UI already removes the deleted plugin's log row, but reinstalling the same key no longer resurrects that old history. This is an explicit lifetime policy change, not a claim of identical observable output in every uninstall/reinstall sequence.
Review correction, 2026-09-18
Activation rechecks approval after waiting for an old revision to stop. A concurrent successful uninstall therefore cannot be followed by a stale activation capturing a new log ring or starting another worker. The controller regression holds the old worker's disposal, completes deactivation and log retirement, then releases disposal and verifies that neither a worker nor a log capture follows. All 35 tests across the manager, controller and three uninstall suites passed; ordinary lint and Node typecheck passed. The before/fixed comparisons and loader controls were rerun with refreshed source hashes. The loader control now accepts both documented publication layouts: dependencies already carrying the fenced context and dependencies still at the named main source.
Remaining limits
This explains a code-level main-process owner that survives successful uninstalls. It does not establish plugin usage, uninstall rate, field allocation size, or attribution for #19831 or any other incident. Installed, disabled, crashed, development and externally deleted plugin histories are not comprehensively retired by this change. Sequential filesystem deletion is not a rollback transaction; failure retains discovery/history for retry. The in-process mutation queues do not make external or cross-process lockfile edits atomic. If a bundled successor appears during shutdown, the old worker may already have stopped before removal is refused; the successor remains installed and can activate afterward.
Audits Plugin Worker Output Retention README (`docs/audits/plugin-worker-output-retention/README.md`)
Plugin worker output retention
The worker output parser capped a line at 8,192 code units, but retained slices could keep a much larger decoded input chunk alive. This artifact reproduces two ownership paths using the actual parser and actual PluginLogBuffer:
- An unfinished line stays in the stream listener's buffer.
- A completed short or truncated line stays in the service's 200-entry log ring.
The fix uses the existing ownRetainedString copier for incomplete segments retained across callbacks and for the bounded string passed to the log sink. Line contents, truncation, callback invocation, ring capacity, and worker lifecycle are unchanged. Strings shorter than 13 code units keep the helper's existing fast path.
Production reachability and lifetime
src/main/plugins/plugin-host-process.tsinstalls the parser on child stdout and stderr at lines 101–102, with UTF-8 decoding in the parser. The production sink passes throughplugin-worker-manager.ts:148andplugin-service.ts:94toplugin-log-buffer.ts:14, which stores the original string without copying it.- The parser retains at most one incomplete line per stream. The default five active workers allow ten live stdout/stderr buffers. Worker slots are acquired before startup. Idle workers are reaped after five minutes, checked every minute; stream end clears parser buffering.
- The log ring belongs to the long-lived
PluginService, not the worker. Worker exit and stream end preserve its last 200 entries per plugin. Ring eviction releases the entries. Several lines can share one parent; the backing allocation must be counted once. - This is a main-process plugin path. The plugin-system setting gates activation (
src/main/startup/main-process-plugins.ts:59–62). It is not a terminal daemon or renderer retention path. The pluginorca.logIPC message is a separate producer. PluginService.getLogsand its IPC handler expose the existing ring. Reading or serializing a concatenated string can flatten it and shorten its parent retention, but does not remove the service's ring entries.
Reproduce
From the repository root with the project's dependencies installed:
ORCA_BACKGROUND_LAUNCH=1 node --expose-gc --max-old-space-size=192 docs/audits/plugin-worker-output-retention/reproduce.cjsFor Electron, run the installed Electron executable with ELECTRON_RUN_AS_NODE=1, ORCA_BACKGROUND_LAUNCH=1, and the same arguments. This uses Node mode without opening an app window. For example, on macOS:
ORCA_BACKGROUND_LAUNCH=1 ELECTRON_RUN_AS_NODE=1 node_modules/electron/dist/Electron.app/Contents/MacOS/Electron --expose-gc --max-old-space-size=192 docs/audits/plugin-worker-output-retention/reproduce.cjsThe runner writes node-results.json or electron-results.json beside itself. Pass --output to preserve the captured reports. It uses inert PassThrough streams, no OS child process or network, a 192 MiB heap limit, and a 30-second deadline.
sources.cjs reverses fix.patch in memory and checks exact baseline/fixed parser hashes. It also checks eight dependency/caller hashes and records actual evaluated source and bundle hashes. No source files are overwritten. A synthetic CRLF read control checks all ten source/patch reads.
source-versions.json records identical parser, sink, caller, and helper hashes at main checkpoint 291b4ddd6f1c1af480169885e0fda7f9c78ff053, main f78483ec29891ab11f49bb25e6cd628837b1242e, and the #20960 topic np-oom-scan-retained-text-slices at 0d2efbc0d3b4f902b9bc02f5451c6ff4291405cf. The parser, sink and caller modules also match v1.4.198 (e0826956fcfc532f5a1e55b5e081f2e57e553c43), which lacks the newer own-retained-string.ts wrapper. The baseline bundle uses only the unchanged parser and ring; fixed variants use the recorded publication helper. These are source controls with current build dependencies, not a historical app binary.
Controls and results
Both Node 26.6 and Electron 43.7 / Node 24.21 pass 24 cases: baseline, diagnostic tail-only copy, fixed Buffer copy, and fixed code-unit-copy fallback, each with two input sizes and three ownership cases. The fallback is a shared-helper compatibility control; production main normally has Buffer.
| Retained owner | Baseline heap delta | Fixed heap delta |
|---|---|---|
| Ten unfinished tails, 64 KiB input each | 0.72–0.73 MB | 9–24 KB |
| Eight unfinished tails, 4 MiB input each | 33.56–33.57 MB | 6–11 KB |
| 200 short log rows from 205 × 64 KiB inputs | 13.14–13.16 MB | 27–45 KB |
| 200 truncated log rows from 205 × 64 KiB inputs | 13.14–13.15 MB | 3.29–3.31 MB |
| Eight short log rows, 4 MiB input each | 33.56 MB | about 1 KB |
| Eight truncated log rows, 4 MiB input each | 33.56 MB | 128–132 KB |
Heap deltas include GC noise. Truncated strings legitimately retain 8,192 code units, including the non-ASCII truncation suffix. Tail-only copying fixes unfinished buffers but leaves both log-ring paths. Stream end clears no-op-sink tails while the actual ring remains live; replacing all 200 entries releases the original parents.
64 KiB is an ordinary-scale stdio input control. The 4 MiB input is amplified stress, not a claim about normal OS pipe reads. PassThrough delivers the selected chunk intact; real child-pipe chunk sizes depend on runtime and OS. Retention is bounded by owner count, ring capacity and backing input size; this is not an unbounded line queue.
Behavior comparisons cover blank and split lines, null/empty streams, CRLF, end flushing, discard/resume after overflow, log level, exact ring content, NUL, lone surrogates, emoji, and the code-unit limit. Value comparisons run separately from heap controls because comparing concatenated strings can flatten them and change retention.
Validation
ORCA_BACKGROUND_LAUNCH=1 pnpm exec vitest run --config config/vitest.config.ts src/main/plugins/plugin-worker-output-retention.test.ts src/main/plugins/plugin-worker-output-buffer.test.ts src/main/plugins/plugin-host-process.test.ts src/shared/own-retained-string.test.ts
ORCA_BACKGROUND_LAUNCH=1 pnpm exec vitest run --config docs/audits/plugin-worker-output-retention/before.config.mjs src/main/plugins/plugin-worker-output-retention.test.ts src/main/plugins/plugin-worker-output-buffer.test.ts
ORCA_BACKGROUND_LAUNCH=1 pnpm tc:nodeThe fixed source passes 20 tests. The baseline overlay intentionally fails all three new heap regressions: approximately 33.6 MB for unfinished tails and 13.2 MB for each ring case, against 2 MiB and 5 MiB ceilings; its original behavior test passes. Node typecheck passes. All five changed-quality scan configurations pass over all five product/test/artifact code files with --no-ignore --deny-warnings, including the ordinary and type-aware lint rules.
This proves a reachable code mechanism and its repair. It does not establish affected-host plugin use, output cadence, aggregate app RSS, or attribution to #19831 or another incident.
Audits Pty Detector Retention README (`docs/audits/pty-detector-retention/README.md`)
Retained PTY detector input
The advertised-URL watcher keeps a 4,096-character carry for each bound PTY and
16,384 characters for each of at most 32 unbound PTYs. The Command Code status
detector keeps 300 characters before its agent-specific prefilter, including for
ordinary shell, Claude, and Codex output. Each could keep the whole original
input alive through a V8 sliced string.
The fix uses the existing ownRetainedString copier when dropping oversized
input. It preserves URL reconstruction, status detection, UTF-16 code units,
binding/unbinding, cache limits, and remote/local authority. These are three
additional boundaries in #20960.
Reproduce
ORCA_BACKGROUND_LAUNCH=1 node --expose-gc docs/audits/pty-detector-retention/reproduce.mjsThe script bundles the actual detector and watcher. The baseline removes only
the three copy calls in memory. Each input has its own live owner; URL cases
use separate watcher instances to isolate each carry. Heap is measured after
GC, before completing the partial URLs and verifying cleanup. Results include
owner overhead, not just text. Bundle hashes and measurements.
| Case | Input per owner | Owners | Heap before | Heap after |
|---|---|---|---|---|
| Status detector | 64 Ki characters | 32 | 2,112,688 | 41,400 |
| Bound URL carry | 64 Ki characters | 32 | 2,173,848 | 204,112 |
| URL pending binding | 64 Ki characters | 32 | 2,148,824 | 575,512 |
| Status detector | 4 Mi characters | 8 | 33,557,144 | 6,504 |
| Bound URL carry | 4 Mi characters | 8 | 33,569,168 | 47,112 |
| URL pending binding | 4 Mi characters | 8 | 33,568,936 | 144,504 |
Captured with Node v26.6.0 on macOS. Three GC regression tests failed before the
fix, retaining about 32 MiB each, and pass afterward. The five-suite run passes
164 tests including existing URL/status behavior and copier Unicode/fallback
tests.
Scope and limits
Main feeds both observers before renderer batching. Default daemon bulk output
frames are at most 64 Ki UTF-16 characters; main's later 16 Ki-character batching
does not bound these readers. The 4 Mi-character cases demonstrate the retaining
mechanism under larger inputs, not normal daemon frame size. Both implementations
also exist in v1.4.198.
Transformed frames bypass ordinary chunk slicing but still face the daemon's
16 MiB encoded-line limit. Native fallback output has no application chunk cap;
this audit does not establish multi-MiB native reads. Ordinary relay chunks are
16 Ki characters. The actual main feed is orca-runtime-on-pty-data.ts and the
ordinary daemon bound is in daemon-stream-data-batcher.ts.
These are per-owner last-input costs, not unbounded growth for a fixed set of
PTYs and fixed-size frames. Owners consuming the same input can share the same
backing string, so the three measurements must not be added as independent
process costs. Unbind removes URL buffers and pending entries. This improves
memory proportional to active owners; it does not establish the cause or growth
rate of #19831 or #19768. Copy work is bounded by the small retained tails.
Audits Queued Terminal Graph Exit README (`docs/audits/queued-terminal-graph-exit/README.md`)
Queued renderer graph restores an exited pane owner
This is a separate source-level explanation for part of #19018. It reproduces an execution host certifying exit, followed by a queued renderer graph restoring that PTY's runtime connected flag and making the actual stable-pane resolver throw terminal_pane_owner_conflict against the successor's durable binding.
Run
From the checkout, with installed dependencies:
ORCA_BACKGROUND_LAUNCH=1 node docs/audits/queued-terminal-graph-exit/reproduce.mjs /tmp/queued-terminal-graph-exit.jsonThe script uses the real renderer graph publisher, main Store.persistPtyBinding, runtime, daemon server, adapter, and local sockets. Only the subprocess and the IPC dispatch boundary are controlled. It creates temporary data/socket paths, runs hidden Node tests, and removes its scratch files. It does not launch an Electron window or install dependencies. The JSON records source hashes and excludes randomly allocated terminal handles/incarnations.
Ordering
- Publish the mounted predecessor's graph normally.
- Capture its next unchanged publication at the IPC dispatch boundary. The real publisher sends the mounted leaf,
mobileSessionTabs: [], andunchangedMobileSessionWorktrees. - While that publication is queued, spawn a successor and durably bind it to the same tab and leaf.
- Deliver the predecessor's physical daemon EXIT. The runtime becomes disconnected with an
exitedverdict. - Deliver the already-captured graph, without reordering renderer publications.
- Resolve the pane, query the owning daemon's fresh inventory, and publish once more. A second variant unmounts the renderer terminal before that inventory and remounts it afterward through the real registration/publisher API.
The replacement-binding commit and the daemon EXIT can run while a renderer invocation is queued. The production spawn commit persists the binding before returning its reply (ipc/pty/ipc/spawn-commit-persist.ts); the graph publisher reads the mounted pane's transport independently. The fixture controls that ordering; it does not prove its frequency on the reporting machine.
Retirement correctly refuses to delete the successor's durable binding. Before the fix, the retained surface coordinates then allow the old graph leaf to set the predecessor connected again. A healthy inventory contains only the successor, but the runtime sweep skips records that still have a graph leaf. The list's presentation can label that leaf disconnected while the underlying pane resolver still sees it connected.
Results
| Variant | First queued graph restores predecessor | After unmount, inventory, and remount | Pane conflict |
|---|---|---|---|
| Before | yes | yes | yes |
| Exit check only | no | yes | no at first check |
| Complete fix | no | no | no |
All three variants also run an ordinary-exit control without a replacement; it remains retired throughout. The middle variant isolates why a weak inventory absence during a renderer mount gap must preserve an already-earned exit certificate. Without that mount gap, the retained disconnected leaf keeps the inventory sweep from forgetting the verdict.
The fix reuses the existing liveness verdict registry to keep an exited graph leaf disconnected and nonwritable, and skips recreating its PTY/URL-watcher ownership. It preserves surface membership: a separate actual stopExactTerminalsForWorktree({ keepHistory: true }) control passes a changed renderer-built mobile snapshot while physical exit has completed but the stop reply is pending. The history surface remains present before and after the renderer clears its PTY binding. All phases check this control; the fixed phase also checks that mobile projection never combines one PTY's ID with another PTY's handle.
Fresh spawn/registration clears the prior verdict; an owning inventory can establish live. A physical exit still records its certificate if the bounded PTY archive was already pruned. Host-only tests cover same-ID replacements, stale predecessor EXIT, fresh renderer-only panes, physical negative exit codes, local unverified stops, SSH disconnects, and retained history. The portable proof runs 15 actual-runtime cases across its three source variants.
Limits and version evidence
The relevant graph admission, unconditional graph-connected write, durable retirement refusal, and inventory leaf exception are present in the reported v1.4.197. That tag already passes providerExitObserved from local and daemon physical exit callbacks and computes processDeathCertified; this proof's natural-exit path does not depend on #21000's synthetic-notification correction. Executable before/after runs use the current checkout with narrowly asserted source transforms, not the complete historical binary.
This proves stale runtime/pane ownership, not a measured native-process or heap leak. The graph alone does not recreate a headless terminal model. It does not establish that every missing diagnostics row denotes an exited process, nor explain all handle-count growth in the issue.
The existing register retains at most 256 unowned verdicts; PTY/handle/leaf owners keep their verdicts until their own lifecycle ends. The disconnected PTY archive is capped at 128. After both a record and its bounded verdict are evicted, the graph has no remaining per-ID certificate; this change does not add permanent tombstones. Later loss-of-contact writes can still replace an exit verdict with unverifiable; a stale positive inventory can separately write a connected record. Those paths are not exercised or fixed by this local queued-publication proof.
A separate audit found that an unreachable unrelated legacy daemon can make aggregate exact-stop verification fail despite absence on the target's own daemon. That is excluded from this fix and from the proof's healthy target-inventory assertion.
Recorded run provenance
results.json records the graph fix before the separate provider-inventory lifecycle fence in #21014. Its source hashes identify that earlier run; they are not a claim that every later audit commit has the same bytes. The reproduction can be rerun against the combined worktree.
Pull request dependency
The graph PR is stacked on #21000, reusing its daemon socket fixture and physical-exit delivery contract. The graph mechanism is separate; the stack makes the executable proof dependencies explicit.
Audits Retained Text Slices README (`docs/audits/retained-text-slices/README.md`)
Retained CI and terminal text tails
Capped V8 string slices can keep their entire original input alive. The affected
CI excerpt cache accepts 128 entries of 16 KiB text, from downloads up to 64 MiB.
GitLab's raw-trace clamp reaches the same shared excerpt function. Terminal
session/eager/shutdown buffers, deferred reattach queues, recent-output buffers,
and error surfaces also retained oversized parents despite their logical caps.
The fix reuses the existing shared ownRetainedString copier for CI, persisted
session tails, and main/relay recent output. Renderer queues and errors reuse
their existing flattenRetainedSlice helper. Content, Unicode, earlier-error
selection, cache counts, and transport payloads stay identical. Ordinary
untruncated terminal chunks keep their existing path. Main/relay recent output
preserves chunk boundaries for path-candidate backfill.
Local persisted scrollback is already pruned; the session-buffer fix primarily
covers remote or not-yet-classified owners. Queue and error fixes cover local
and remote output. The main/relay recent-output buffer has a configurable cap,
64 Ki characters by default.
Reproduce
ORCA_BACKGROUND_LAUNCH=1 node --expose-gc docs/audits/retained-text-slices/reproduce.mjsThe script bundles actual production functions. Its baseline removes only the
six new copy boundaries in memory; production files are not changed. Each case
retains eight distinct inputs: 2 Mi characters per CI log and 4 Mi characters
per terminal input. It measures heap after GC and clears V8's independent legacy
RegExp input reference. Bundle hashes and measurements are in
results.json.
| Case | Returned bytes, all eight | Retained heap before | After |
|---|---|---|---|
| GitHub long line | 131,072 | 16,787,512 | 145,224 |
| GitHub earlier Unicode error | 131,064 | 33,577,840 | 112,744 |
| GitLab long line | 131,072 | 16,793,640 | 147,312 |
| Persisted terminal buffers | 4,194,304 | 33,555,624 | 4,195,032 |
| Eager/pre-handler/shutdown tails | 4,194,304 | 33,556,040 | 4,202,608 |
| Main/relay recent output | 524,288 | 33,558,752 | 527,384 |
| Terminal error surfaces | 32,000 | 33,555,864 | 33,336 |
| Deferred reattach tails | 4,194,304 | 33,565,384 | 4,198,352 |
Captured on macOS with Node v26.6.0. Heap samples include allocator/GC variation;
the large separation is the relevant result. Regression tests also retain the
actual error state and shutdown/reattach/recent-output queue objects.
Validation passed: 41 tests across six CI/provider/helper suites; 69 tests across
six terminal storage/ownership/UTF-8 suites; 28 tests across three error/reattach
suites; and a final 63 tests across seven recent-output/CI/terminal/copier suites.
These are per-run counts and overlap. Full typecheck and changed-code quality pass.
All six cap/slice paths exist in v1.4.198. Neither #19831 nor #19768 establishes
the CI-log viewing or oversized terminal inputs required for incident attribution.
Copying costs scale with retained caps: 16 KiB per CI excerpt, 4,000 characters
per error, 512 KiB for the largest byte-capped buffer, and 512 Ki characters for
deferred reattach. The change does not reduce temporary original-input allocation.
The follow-up PTY detector reproduction
adds three boundaries in the same PR: advertised-URL carries, output waiting for
workspace binding, and Command Code status carries used by ordinary PTYs too.
Thirty-two production-sized 64 Ki-character inputs retain about 2.1 MB in each
isolated baseline. Owned carries reduce that to about 41 KB, 204 KB, or 575 KB,
including the different owner objects. These per-owner costs are not an
unbounded growth curve, and readers of the same input can share its parent.
The follow-up adds 164 passing tests across five detector/URL/copier suites.
The Claude task metadata reproduction adds
the shared 512-character description/name boundary. JSON-parsed task frames
retained their parents in the actual live, settled, and recently removed tracker
entries. Eight 4 Mi-character inputs retained about 32 MiB before the copy and
7–12 KB afterward; 32 smaller 64 Ki-character inputs retained about 2.1 MB before
and 25–45 KB afterward. These synthetic fields establish a retaining mechanism,
not the trigger of a reported incident.
Audits Runtime Rpc Consumed Queue README (`docs/audits/runtime-rpc-consumed-queue/README.md`)
Completed RPC queue records remain reachable
Status: reproduced before and after the product fix on Node 26 and installed Electron 43.7.0 in Node mode.
RuntimeRpcCallQueuePool advances each lane's head without clearing the consumed array element. While any call keeps that selector active, completed records keep their run, resolve, reject, and signal fields reachable until that same lane compacts. Compaction requires more than 32 consumed entries and at least half the array consumed. Selector deletion also releases the arrays when every active and queued call finishes.
The fix assigns undefined to the consumed slot in takeForeground and takeBackground and permits undefined in the two array element types. An active call remains owned by its promise cleanup closure. Queued entries, both lane heads, counts, admission thresholds, batching, foreground preference, and cancellation behavior stay intact.
Production reachability
- Desktop
src/main/ipc/runtime-environment-call-queue.tsowns a module singleton.runtime-environment-transport-routing.ts::callRuntimeEnvironmentpasses a closure capturing request params, environment, and optional orchestration envelope. Its normal timeout is 15 seconds;status.getbypasses this queue. - Paired web
src/renderer/src/web/preload-api/web-runtime-session.tsowns another module singleton.web-runtime-calls.tsandweb-filesystem-api.ts::captureWebFileMutationSessionpass closures capturing params, environment, and sometimes an explicit client. Web request timeout defaults to 30 seconds after connection readiness. - Current production callers pass/default
retainedBytesto zero. The nonzero byte values in the proof exercise the queue's accounting; they are not evidence that deployed callers account their object graphs here. - These are remote/paired execution paths. The finding does not explain local-only #19831 from code reachability alone.
Finite individual call duration does not guarantee that an old lane's consumed records disappear: overlapping calls in the other lane can keep the selector active. The extended proof completes 70 successive background calls while eight completed foreground payloads stay reachable before the fix; each background call finishes, and releasing the final call makes the selector idle and permits collection.
This is retention beyond useful lifetime, bounded in record count by existing compaction/admission behavior. It is not proof of unlimited growth for one selector or of any reported incident's magnitude. The isolated payloads are bounded dummy arrays rather than a real network workload.
Proofs
reproduce.cjs bundles the actual queue module with its actual imports. queue-source.cjs reconstructs the baseline in memory by reversing fix.patch; both baseline and current source hashes must match source-versions.json. Eight completed calls capture eight 1 MiB typed-array payloads, with one other call holding the selector active. Weak references remain live before and all clear after the fix. Foreground and background lanes, eventual compaction, and eventual idle cleanup are covered. Queue byte credit and queued-call count already equal zero during stale retention. At most eight payload MiB are intentionally live in each fixture; the process has a 128 MiB old-space limit and a ten-second deadline.
extended-controls.cjs checks active payloads are retained until their call settles, cancellation releases queued payloads without waiting for unrelated active calls, synchronous failure releases only after compaction/idle before the fix, rolling cross-lane traffic, and a 140-call mixed burst with six cancellations. Both variants execute the same 134 remaining calls in FIFO lane order with foreground priority, then delete the idle selector.
resolver-controls.cjs isolates the runtime's settled-promise resolver behavior without using the queue. Keeping native resolve functions can also keep settled results reachable in some runtime versions; this must be reported separately from input closures.
Node and Electron results are stored separately. Node 26.6.0/V8 14.6 collects fresh response payloads even with the stale queue records. Installed Electron 43.7.0/Node 24.21.0/V8 15.0 retains all eight fresh response payloads before and releases them after the fix. The standalone resolver control reproduces the same difference: saving native resolve functions retains eight of eight payloads in Electron and zero in Node; releasing those functions permits collection in both. This control isolates runtime promise behavior without claiming all Electron versions or browser renderer modes behave identically.
The original Node-only negative-control expectation for response retention failed under Electron. The baseline now records that result rather than assuming every V8 version releases settled results identically. The fixed variant must release responses in both environments. This proof does not measure the exact Electron 43.4.1 historical binary.
Run from the worktree:
ORCA_BACKGROUND_LAUNCH=1 node --expose-gc --max-old-space-size=128 docs/audits/runtime-rpc-consumed-queue/reproduce.cjs
ORCA_BACKGROUND_LAUNCH=1 node --expose-gc --max-old-space-size=128 docs/audits/runtime-rpc-consumed-queue/extended-controls.cjs
ORCA_BACKGROUND_LAUNCH=1 node --expose-gc --max-old-space-size=128 docs/audits/runtime-rpc-consumed-queue/resolver-controls.cjs
ORCA_BACKGROUND_LAUNCH=1 node node_modules/vitest/vitest.mjs run --config config/vitest.config.ts src/shared/runtime-rpc-call-queue.test.ts src/shared/runtime-rpc-call-queue-retention.test.tsThe installed Electron binary can run the same scripts with ELECTRON_RUN_AS_NODE=1, ORCA_BACKGROUND_LAUNCH=1, and the same Node flags. No Electron app or window is created. No network, microphone, or affected-host data is used. No heap-snapshot tools are exposed in this session; the proof measures WeakRef reachability and bounded process counters instead of reading raw heap snapshots.
The existing eight queue tests and six added retention/lifecycle tests pass with the fix. To reproduce the three retention failures against the reconstructed baseline, use ORCA_BACKGROUND_LAUNCH=1 node --expose-gc node_modules/vitest/vitest.mjs run --config docs/audits/runtime-rpc-consumed-queue/baseline.config.mjs src/shared/runtime-rpc-call-queue.test.ts src/shared/runtime-rpc-call-queue-retention.test.ts; the expected outcome is 11 passing tests and three failures, with exit code 1. Node and Web project typechecks and the changed-code quality gate passed during promotion. Explicit basic/type-aware lint also covers these otherwise ignored audit scripts.
Source identity
The queue source before the fix, fetched origin/main, and v1.4.198 all had SHA-256 45921658a10ffeefe0247673227aab643d1b5251d24aa460244fadd5480fc349 at review. source-versions.json records exact baseline/current hashes and compared commit IDs. Availability in that release supports reachability analysis; it does not attribute an incident.
Audits Scanner Late Cancel README (`docs/audits/scanner-late-cancel/README.md`)
AI Vault scanner late cancellation
The scanner child kept cancellation IDs after their requests had already settled.
Its response can still be in transit when the parent sends a cancellation, so this
does not require an invalid caller. The completed request has already run its
cleanup; nothing remains to delete the newly inserted ID.
The fix admits cancellation only while the existing pending set owns the request.
That set includes both queued and running requests. Their cancellation and cleanup
remain unchanged. No protocol or history-retention policy changes.
Proof
Run from the repository root with dependencies installed:
ORCA_BACKGROUND_LAUNCH=1 node --max-old-space-size=128 docs/audits/scanner-late-cancel/reproduce.mjsThe script loads the checked-out production entry and derives the before version by
removing only the three-line pending membership guard in memory. It checks that
the guard occurs exactly once; no historical commit or Git access is needed.
Esbuild strips TypeScript before both versions run in separate VM contexts. Only
imported collaborators are stubbed: the production message handler, request lanes,
sets, and cleanup run. After each synthetic first-prompt request completes, its
matching cancel arrives.
The script asserts the counts and emits JSON with both source SHA-256 hashes and
Node/platform/heap-limit provenance. results.json records a run on
Node v26.6.0 with a 128 MiB old-space limit. This measures retained entries, not RSS.
| Source | Requests/responses | Pending | Controllers | Retained cancel IDs |
|---|---|---|---|---|
| Before | 1,000 / 1,000 | 0 | 0 | 1,000 |
| After | 1,000 / 1,000 | 0 | 0 | 0 |
The regression test imports the production entry and directly observes its existing
cancellation set through an admitted cancellation. It repeats late cancels after
both successful and failed requests, verifies queued/running cancellation, and
checks shutdown cleanup. No production diagnostics or test-only exports were added.
ORCA_BACKGROUND_LAUNCH=1 node node_modules/vitest/vitest.mjs run --config config/vitest.config.ts src/main/ai-vault/session-scanner-service-cancellation.test.ts src/main/ai-vault/session-scanner-service-entry.test.ts src/main/ai-vault/session-scanner-service-client.test.tsIncident scope
The same unconditional insertion exists in release v1.4.198. This retains numeric
IDs in the scanner child, not transcript contents in Electron main. It is a concrete
small leak; it does not explain the reported roughly 26 MB/s main-process growth in
#19768 or establish the cause of #19831's scope-level peak memory measurements.
Audits Session Search Write Fences README (`docs/audits/session-search-write-fences/README.md`)
Session-search write fence retention
The search writer remembered every removed path for its lifetime. Those counters
fenced a read whose source disappeared before its first commit: both the original
and deleted database cursors are absent, so comparing cursors alone cannot detect
the removal. Counters for paths with no remaining reads were never released.
The fix tracks only active reads. Removal marks their captured lifetime as removed
and releases the path from the map immediately. New reads get a fresh lifetime;
cleanup from an older read cannot delete it. Final commit and explicit discard
release ownership, while intermediate chunk commits keep it. Consumer errors,
incomplete reads, throwing error reporters, and store close release their fences.
Reproduce
From the repository root with dependencies installed and a Node version providingnode:sqlite:
ORCA_BACKGROUND_LAUNCH=1 node --max-old-space-size=128 docs/audits/session-search-write-fences/reproduce.mjsThe script bundles the actual writer twice, using the production SQLite schema and
adapter. The baseline is published commit243f4431557471daa05636aed1a30be790485eda (#20551), whose writer matches the pre-fix
source. The after version is the working tree. Only this named Git object is read;
the script fetches nothing. Results include both source hashes and runtime details.
After 1,000 complete index/retire cycles:
| Source | Remaining file rows | Retained path entries | Never-indexed stale commit accepted |
|---|---|---|---|
| Before | 0 | 1,000 | No |
| After | 0 | 0 | No |
The regression suite additionally drives the actual consumer/channel path, checks
intermediate flush ownership, failed/incomplete reads, throwing error reporters,
new-generation protection, idempotent discard, and close:
ORCA_BACKGROUND_LAUNCH=1 node node_modules/vitest/vitest.mjs run --config config/vitest.config.ts src/main/ai-vault-search/session-search-write-lifetime.test.ts src/main/ai-vault-search/session-search-file-write.test.ts src/main/ai-vault-search/session-search-index-writer.test.ts src/main/ai-vault-search/session-search-index-consumer.test.tsExisting retry bookkeeping can recreate a failed files metadata row when a
removed read finishes. Its session/messages stay absent and it cannot restore
searchable content. This patch preserves that status policy.
This is current-code path metadata in the scanner child. The search writer did not
exist in v1.4.198; this finding does not explain the reported #19831/#19768 build.
Audits Ssh File Metadata Retention README (`docs/audits/ssh-file-metadata-retention/README.md`)
SSH file readers retain unrelated streams before metadata
The file reader queued every file-stream notification while awaiting its own metadata. A delayed read therefore retained payloads from other reads that had already completed. The fix installs metadata through the mux's existing synchronous beforeResolve callback and ignores notifications until the read has a stream identity. Listeners still register before the request, and own frames adjacent to the response are processed correctly.
This is conditional transient retention during a pending metadata request. The proof establishes a source mechanism and its correction; it does not identify an affected host, measure a natural native I/O stall, or attribute #19831 to SSH.
Actual producer and ownership chain
- Desktop
filesystem-read-handlers.tscalls the selectedSshFilesystemProvider.readFileforfs:readFile; this route does not serialize reads. Runtime previews also use the provider with caller-specific caps. An AI-vault scan has an eight-operation gate, which still permits repeated completions in other slots while one operation waits. readFileViaStreamsubscribes to chunk/end/error notifications before sendingfs.readFileStream. Previously it appended all such notifications until the metadata promise's.thencallback ran, even when they belonged to other streams.- Relay
FilesystemHandlerforwards the path and request context toreadRelayFileStreamMetadata. The producer awaitsstatbefore acquiring a stream slot. For unknown MIME types, its prefix probe also precedes registration. After opening/registering the file, it schedules its pump withsetImmediateand returns metadata. RelayDispatcherpublishes the small metadata response in its control lane; the writer prioritizes control before bulk. The saturated-writer control verifies metadata precedes chunks after drain.- The mux runs
beforeResolvesynchronously during response dispatch, before resolving the request promise. Its decoder can dispatch adjacent notifications before any.thencallback runs. The fix installs the stream ID and buffer at that synchronous boundary, eliminating the need to save foreign frames.
The producer, mux, dispatcher, decoder, writer, file I/O, and stream registry are actual source in the portable proof. The fixture connects both ends through an in-memory duplex transport, uses real temporary files, and supplies the filesystem handler's small path/client/pacing adapter. It does not launch an SSH process, Electron window, native PTY, or network server.
Bounds and payload sharing
- The relay allows 16 concurrent registered streams, with a four-chunk ACK window per paced stream. Chunks are 256 KiB. A metadata operation waiting before registration occupies no stream slot. Other transfers can complete and reuse slots repeatedly.
- Reader size caps are 10 MiB text / 50 MiB binary, optionally tightened by the caller. They apply after that reader's metadata and do not charge foreign history accumulated before it.
- The metadata request has a 30,000 ms deadline. After metadata, the reader uses a 60,000 ms inactivity deadline, reset by its own chunks and integrated with suspend/resume. Connection disposal also releases subscriptions. These timers and transport throughput bound ordinary retention duration; suspension/event-loop stalls can delay timers. No indefinite native stall was established.
- The decoder limits each turn to 64 frames / 4 ms and bounds retained framing bytes. Those limits do not bound arrays owned by subscribers after frames are parsed.
- The mux passes the same parsed params object to all subscribers. Four waiting readers add four wrappers per frame, but share its payload. The result is not four copied payloads or quadratic payload-byte growth.
Comparative results
All 80 portable cases pass: ten controls × baseline/fixed × audited-worktree/named-main graph × Node/Electron. Node is 26.6; Electron 43.7 uses Node 24.21. Reports record exact versions, all 59 selected source hashes, the observed reader hash, and proof artifact hashes.
| Observation | Before | Fixed |
|---|---|---|
| Four waiting readers; 16 completed 2 MiB transfers | 576 wrappers | 0 |
| Unique shared params objects retained | 144 | 0 |
| Logical base64 bytes, counted once per unique params object | 44,739,584 | 0 |
| Peak registered streams in that workload | 1 | 1 |
| ACKs processed | 128 | 128 |
| Reader history after metadata completion, handled disposal, or deadline | released | released |
| Actual pump with ACK delivery withheld | stops after 4 chunks | same |
| Sixteen active streams, then a seventeenth request | refused; later admission succeeds | same |
| Saturated writer, then drain | metadata before own chunks | same |
| Response plus own chunk/end in one decoder turn | correct result | same |
| Unpaced producer / ordinary completion | correct result | same |
| Canonical LF vs synthetic CRLF source/patch reads | 66 reads agree | same |
The primary portable workload deliberately gates four request handlers immediately before invoking the actual relay file producer. The request remains pending while other real transfers complete. This controlled adapter delay is distinct from a native stat already in progress; production source establishes that awaiting stat occurs at the same pre-registration phase. It does not measure how often or how long native metadata I/O delays occur on a user's machine.
The baseline observation adds only WeakRef(pending) to expose the closed-over array. It does not add a strong owner. Shared params identity is checked across all waiting readers. Heap deltas support the object/byte accounting but are neither exact object sizes nor RSS. After disposal, the test consumes lazy Error.stack and retains only error codes: externally retained unmaterialized V8 error stacks can themselves retain callback context, so the release claim is after normal error handling.
Every successful transfer checks payload length and SHA-256. Stream capacity, pacing, cancellation, and output assertions run identically for both variants. The controlled producer ignores ACK pacing in one case; this tests existing unpaced behavior, not every historical relay binary.
Source graphs and publication
source-versions.json records the full 59-module import graph and five additional actual caller hashes. Both graphs select the same file-reader source variant. The audited worktree and named main 291b4ddd6f1c1af480169885e0fda7f9c78ff053 otherwise differ only in the previously published SSH writer consumed-prefix correction.
main-context.patch reconstructs that single context difference in memory. The loader accepts either of its two exact recorded checkout hashes and reconstructs the selected graph. This lets the same artifact run on this worktree or the independent main publication without depending on another memory PR. fix.patch is the separate, single-product-file change under review. Every other graph/caller source is hash-fenced; unknown production imports fail. Dedicated portable tests omit unrelated global Vitest setup files.
The current reader baseline and relay file producer are also byte-identical to v1.4.198 (e0826956fcfc532f5a1e55b5e081f2e57e553c43). That named version has synchronous beforeResolve and control-first writer scheduling. Its comparison is limited to the recorded paths; the proof does not execute a whole historical application.
No wire field, opcode, host execution verdict, stream cap, timeout, fallback, or native process lifetime changes. Existing MethodNotFound fallback and malformed-metadata / tighter-cap / empty-image / adjacent-error handling are covered through the actual mux by the permanent regression suite.
Reproduce
Choose either graph (worktree or main) and variant (before or fixed):
ORCA_BACKGROUND_LAUNCH=1 ORCA_SSH_READER_GRAPH=main ORCA_SSH_READER_VARIANT=fixed pnpm exec vitest run --config docs/audits/ssh-file-metadata-retention/vitest.config.mjsFor Electron, invoke the installed Electron binary with ELECTRON_RUN_AS_NODE=1 and ORCA_BACKGROUND_LAUNCH=1, passing node_modules/vitest/vitest.mjs and the same arguments. Reports are separate for every graph/variant/runtime. Set ORCA_SSH_READER_OUTPUT to an alternative file path to preserve captured reports.
Permanent tests and the intentional baseline failure:
ORCA_BACKGROUND_LAUNCH=1 pnpm exec vitest run --config config/vitest.config.ts src/main/ssh/ssh-filesystem-stream-retention.test.ts src/main/providers/ssh-filesystem-provider-stream.test.ts src/main/providers/ssh-filesystem-provider.test.ts src/main/ssh/ssh-channel-multiplexer.test.ts src/relay/fs-handler-stream.test.ts
ORCA_BACKGROUND_LAUNCH=1 pnpm exec vitest run --config docs/audits/ssh-file-metadata-retention/before.config.mjs src/main/ssh/ssh-filesystem-stream-retention.test.ts src/main/providers/ssh-filesystem-provider-stream.test.tsThe baseline keeps all 64 observed foreign frame objects while metadata remains pending, causing exactly the new lifetime assertion to fail; the other 22 tests pass. The initial four-suite run passed 70 tests. Detailed quality/typecheck results are in validation.json. Full-file casting diagnostics are the same 15 inherited assertions in the original reader and provider test, verified by exact diagnostic/source-span comparison; the changed-code gate reports no new findings. No lint rule was suppressed and no unrelated wire validation behavior was changed to satisfy that baseline cleanup.
The expanded five-suite run passes 124 tests, including the general provider suite. The empty-file control uses the existing streaming fixture, which invokes the mux's beforeResolve callback before resolving metadata, and verifies all stream listeners are released. The older generic fixture omitted that callback and reproduced the CI timeout; actual-mux empty metadata controls already passed. This correction changes test setup only.
Audits Ssh Writer Consumed Prefix README (`docs/audits/ssh-writer-consumed-prefix/README.md`)
Completed SSH writes retained behind a rolling backlog
The SSH multiplexer lane scheduler advanced its read index without clearing consumed entries. A lane that stayed nonempty retained every completed WriterEntry, its encoded buffer and its settlement callback. The writer had already released those entries from its byte and frame counters, so admission limits did not bound this consumed prefix. Fully draining the lane or disposing the multiplexer released it.
The fix clears the selected slot and compacts a consumed prefix after at least 1,024 selections when it occupies at least half the array. This follows the existing RelayFrameBuffer pattern. Lane ordering, fairness, admission limits, transport settlement and in-flight ownership are unchanged. clear() returns only remaining live entries.
Actual caller and ownership
The ordinary-lane proof executes writeToSshPtyWithSettlement → SshChannelMultiplexer.notifyWithSettlement → SshMultiplexerTransportWriter → SshMultiplexerWriterLaneScheduler. SshPtyProvider exposes the same helper through its RPC operations. The control-lane proof uses the git.responseAck notification shape emitted by requestGitStreamable.
ssh-relay-deploy-helpers.ts connects transport writes and settlement callbacks to channel.stdin.write, and registers its drain event. A producer that keeps at least one queued entry behind repeated backpressure/drain cycles reaches the retained-prefix state. The proof exercises both a controlled callback/drain port and a real Node Writable with a deferred write callback and a synthetic 16 KiB high-water mark. No SSH connection, app, window or remote process is launched.
Selecting an entry transfers scheduler custody to the writer's in-flight set. Clearing its consumed queue slot does not complete the write. A separate control disposes with an in-flight and a queued write: their existing results remain unverifiable and refused, respectively. The native callback can still retain the in-flight buffer until that callback reference is released. Late and duplicate callbacks do not settle it twice.
Results
Both captured runtimes produce the same counts: Node 26.6 and Electron 43.7 / Node 24.21. Each runs six scenarios before and after the fix.
| Scenario at the controlled pause | Original | Fixed |
|---|---|---|
| Ordinary lane: completed buffers and settlement callbacks retained | 2,048 each | 0 |
| Control lane: completed buffers and settlement callbacks retained | 2,048 each | 0 |
| Physical queue slots after those selections | 2,050 | 2 |
| Logical queued ordinary frames / bytes | 2 / 706 | 2 / 706 |
| Logical queued control frames / bytes | 2 / 184 | 2 / 184 |
| Real writable: retained written buffers, including one in flight | 128 | 1 |
| In-flight buffer retained after disposal while native callback is owned | 1 | 1 |
| Written buffers retained after complete drain or final callback release | 0 | 0 |
The real-writable scenario completes 128 writes, starts the 129th and keeps two further writes queued. The first write preceded the rolling backlog and is collectible in both variants; the original therefore retains 127 completed buffers plus the in-flight one. Its logical budget is three frames / 49,440 bytes in both variants. Empty slots below the compaction threshold are expected and do not retain those buffers.
The scenarios assert FIFO order, isolation of ordinary/control counters, full-drain and disposal cleanup. Five permanent lifetime regressions plus 26 existing tests pass. The original-source overlay fails the four rolling-backlog regressions and passes the other 27 tests. Existing tests cover control priority and starvation prevention, liveness bypass, synchronous drain, callback errors and duplicates, overflow, disposal, timeouts and slow-but-live transport handling. See validation.json for commands and full-publication quality checks.
Reproduce
From the repository root after dependency installation:
ORCA_BACKGROUND_LAUNCH=1 node --expose-gc --max-old-space-size=192 docs/audits/ssh-writer-consumed-prefix/reproduce.cjsFor Electron, run the installed Electron executable with ELECTRON_RUN_AS_NODE=1 and ORCA_BACKGROUND_LAUNCH=1, passing the same flags and script path. This is a Node-mode process with no UI. The runner has a 20-second deadline.
An optional final argument selects the report destination, for example notes/ssh-writer-consumed-prefix/reviewer-node.json. Without it, the runner refreshes the corresponding artifact node-results.json or electron-results.json.
sources.cjs reverses fix.patch in memory and verifies both original and fixed SHA-256 hashes. It also verifies all nine bundled source dependencies against the recorded hashes. No source file is rewritten. A synthetic CRLF read of the patch and source must reproduce identical canonical LF sources and hashes. before.config.mjs uses the same source loader for the original-source test overlay.
Source compatibility and scope
source-versions.json records the exact scheduler baseline at the pre-fix audit commit, independent main 291b4ddd6f1c1af480169885e0fda7f9c78ff053, and v1.4.198 e0826956fcfc532f5a1e55b5e081f2e57e553c43. These scheduler sources are byte-identical after LF normalization, so the same patch yields the same fixed hash. All nine bundled sources and three additional caller sources match independent main. The projected main change has no dependency on the other memory-audit fixes.
The v1.4.198 scheduler is identical, and its writer contains the same enqueue/select/release path. Seven of the twelve dependency/caller files differ from current source; this artifact does not claim to execute the packaged historical release.
Inputs and timing are controlled fixtures. Reachability counts establish the code-level retention mechanism; they do not measure affected-host RSS, model a reported growth rate, or establish that an incident had a continuously nonempty SSH write lane. Active pending writes, transport-owned callbacks and retained primitive sequence timestamps remain governed by their existing limits and lifecycles.
Audits Stale Pty Inventory README (`docs/audits/stale-pty-inventory/README.md`)
Stale provider inventory overwrites newer PTY ownership
A provider inventory started before a process exit can finish afterward and reconnect the exited runtime record. If a replacement was admitted under the same ID, the old response can also overwrite its incarnation, invalidate its terminal handle, and clear its pane binding.
This is a separate source-level explanation for stale terminal ownership such as #19018. The proof does not establish the cause of #19768 or #19831, or measure their reported memory growth.
Reproduce
From a checkout with dependencies installed:
ORCA_BACKGROUND_LAUNCH=1 node docs/audits/stale-pty-inventory/reproduce.mjsThe script runs the actual runtime inventory, registration, spawn, and exit methods with deferred provider responses. It uses temporary Vitest configuration to reverse only fix.patch for the baseline, leaves checkout files untouched, and removes its temporary files. It launches no app, enumerates no real processes, and installs nothing. Node subprocesses use the repository's portable runProcess implementation.
results.json records source hashes and the before/after result: 16 failing / 6 passing tests before; all 22 passing after.
Retaining and ownership path
refreshPtyWorktreeRecordsWithControllerInventorystarts a provider request and captures its generation and liveness observation sequence.- While the response is pending,
onPtyExitrecords a physical exit. A subsequentregisterPtyoronPtySpawnedmay admit a successor, including one absent from the runtime when the request began. - The original inventory returns its older row. Previously, the verdict setter's observation check did not protect the rest of the row processing: handle adoption ran, and
recordPtyWorktreewroteconnected: trueand the old incarnation.
For an exit without replacement, the record could therefore be connected while its verdict still said exited. A later empty listing skips the disconnected-record sweep while a graph leaf remains. In the successor case, adoptControllerTerminalHandle can invalidate the new handle before the old incarnation is written. Fresh positive inventory can restore the successor handle and incarnation; it cannot reconstruct cleared pane coordinates without binding evidence.
The proof exercises these mutations directly. It does not recreate a native process or a headless terminal model. Consequently, it demonstrates incorrect ownership and potential retained records, rather than a quantified heap or native-process leak.
Fix and controls
Accepted spawn, registration, and exit events now invalidate pending inventory for their provider through the existing provider generation registry. The existing stale-response check runs before handle adoption, record mutation, or construction of an authoritative live-ID result.
The response is rejected as a whole, so filtered rows cannot become false evidence of absence. A targeted worktree read retries at most once using its original deadline; another invalidation yields an unknown result. A targeted different-host query remains valid. Aggregate requests conservatively reject when a provider changes during the request. This can reject a response that happened to include the newly admitted process; a subsequent fresh request remains authoritative.
The tests cover stale positive rows, stale absence, unknown-at-request-start spawns, registrations with no incarnation, ignored predecessor EXIT, exported handle preservation, fresh discovery and replacement, local/SSH routing (including SSH aliases with spaces, @, and :), concurrent provider generations, and bounded retries. Existing partial-relay and liveness tests verify that failed contact is not promoted to process death. The change adds no permanent per-PTY registry and changes no wire fields or liveness vocabulary.
Version and scope
The relevant await, guarded verdict update, unconditional handle/record updates, and spawn/registration verdict deletion exist in v1.4.198. That version forgot the prior verdict on a positive inventory row; current code records live. Both versions leave subsequent mutation outside that observation fence. The executable baseline is current source with the narrow fix reversed, not a historical application binary.
The graph-publication fence is a separate fix. Later loss-of-contact writes and independent paths that query provider inventory directly remain outside this patch. Incident frequency and the reporting machines' actual ordering are unproven.
Audits Structured Hold Retention README (`docs/audits/structured-hold-retention/README.md`)
Structured session hold lost during resume
Run from the repository root:
ORCA_BACKGROUND_LAUNCH=1 node docs/audits/structured-hold-retention/reproduce.mjsThe script bundles the actual StructuredAgentSessionHolds implementation into temporary CommonJS
modules, loads them normally, and removes their files and module-cache entries. It runs the code
once without the post-resume holder check and once with the current source. It uses a deferred
provider acquisition, an isolated fake child, and a 5 ms release grace. It launches no application,
provider, or terminal and reads no user profile.
The last surface releases its hold while acquisition is pending. At that point the session has no
provider child, so release() cannot arm the release clock. Before the fix, acquisition completes
with a child, zero holders, and no scheduled eviction. With the fix, successful acquisition checks
for surviving holders and schedules the existing release clock. The recorded child is released once.
The RPC path registers connection cleanup before awaiting host.hold() insrc/main/runtime/rpc/methods/structured-agent-session-hold.ts. Runtime socket close callscleanupSubscriptionsForConnection() in runtime-rpc/runtime-rpc-lifecycle.ts. That supplies the
production release-during-acquisition ordering reproduced here.
Ownership limits
- This proves a lifecycle race, not that it caused any particular OOM report. No process RSS was
measured. It applies to structured sessions acquiring a provider child, not ordinary PTY tabs. - The release clock preserves its 15-second production grace, waits while a turn is active, and
cancels when a new holder arrives. Acquisition failures retain their existing handling. - Disposal prevents late acquisition or release callbacks from restarting the clock. Host teardown
owns cleanup after disposal. The host's broader pre-attach shutdown admission is outside this fix. - A restored childless journal is not necessarily abandoned. Startup selects persisted visible
tabs;host.sessionssupplieslistSessionTabs(), and childless sessions can retain live TUI
owners.host.close()closes that TUI owner before removing the journal. This fix neither evicts
childless history nor infers process exit from transport loss. - Execution remains on the owning runtime, with no wire or SSH routing changes.
Targeted regressions live insrc/main/native-chat/agent-session-wire/structured-agent-session-hold-resume-race.test.ts.
They cover last-holder loss, active turns, new holders, reconnection, failed acquisition, explicit
close, and disposal.
Same-ID replacement is fenced at both ownership layers. Holder entries receive a new incarnation
after release and re-add, so an old failed acquisition cannot remove a replacement. The RPC uses
the subscription registry's releaseIfCurrent() cleanup, so its failure cannot unregister the
replacement's connection cleanup. Duplicate adds remain one holder. Class tests and real host/RPC
tests cover the old failure arriving before and after replacement success; disconnect still releases
the replacement normally. They also cover the reverse outcome: an old acquisition succeeds and the
replacement refuses a stale fence. Its last-holder rollback starts the same turn-aware release clock
for the acquired child. No RPC fields or published frame shapes change.
Audits Terminal Close Observed Exit README (`docs/audits/terminal-close-observed-exit/README.md`)
Preserve an observed exit during explicit terminal close
An explicit close can receive the target daemon's physical EXIT, then fail its aggregate verification because another preserved daemon is unavailable. The close method used to invoke the fallback kill even though the runtime already held an exited verdict. That redundant request emitted a synthetic -1, replacing operator_close with unknown/stop_unverified and sending a second renderer exit notification.
The fix captures the stamped PTY incarnation before awaiting the stop. A false stop result is accepted only when the same incarnation remains current and the runtime already has an exited verdict. It does not create an exit certificate from an empty inventory or transport failure.
Reproduce
From the checkout, with dependencies already installed:
ORCA_BACKGROUND_LAUNCH=1 node docs/audits/terminal-close-observed-exit/reproduce.mjs /tmp/terminal-close-observed-exit.json
ORCA_BACKGROUND_LAUNCH=1 node node_modules/vitest/vitest.mjs run --config config/vitest.config.ts src/main/runtime/terminal-close-observed-exit.test.tsThe script runs eight scenarios before and after the change, reversing only the new capture and guard for the before variant. It uses the actual runtime close method, runtime controller, daemon router, and two real daemon socket endpoints. The subprocess itself is controlled by the existing test harness. The script uses temporary configuration files, checks the expected outcomes, records source hashes, and removes its temporary directory. It does not install dependencies, launch a UI, or alter the checkout. results.json preserves the recorded result; use a separate output path when rerunning.
| Scenario | Before | After |
|---|---|---|
| Target physical EXIT received; unrelated daemon unavailable | Close returns false; one redundant kill; cause overwritten; two renderer exit notifications | Close returns true; no redundant kill; operator_close preserved; one renderer exit notification |
| Healthy aggregate inventory, delayed physical EXIT | Close succeeds | Unchanged |
| Target socket paused; unrelated daemon unavailable | Close remains unverifiable despite target's empty inventory | Unchanged |
| Same stamped incarnation already exited | Redundant fallback kill | Existing exit accepted |
| Same raw ID registered with a newer incarnation | Old certificate rejected | Unchanged |
| Synthetic negative exit, no host exit certificate | Close remains unverifiable | Unchanged |
| Unstamped legacy session | Certificate not reused | Unchanged |
| Stop throws after exit | Catch records unverifiable | Unchanged |
In all socket scenarios, the physical provider event and runtime exit listener settle once. The fixed observed-exit case has no headless model or title tracker retained. This proof measures lifecycle behavior, not retained heap bytes.
Dependency and incident limits
This change is stacked on #21000, branch np-oom-scan-daemon-late-exit, and reuses its actual daemon socket fixture and late physical-exit reconciliation. #21000 fixes final DATA arriving after a synthetic exit. This change prevents a redundant synthetic exit after a physical exit has already been accepted. The before variant is the current checkout with this narrow guard reversed, not a pristine historical build.
The unconditional fallback and exit-cause assignment are present in the reported v1.4.197 source (orca-runtime-stop-explicitly-closed-tab-ptys.ts, orca-runtime-on-pty-exit.ts). They explain a concrete way to get a failed close and stop_unverified despite a confirmed local exit. #19018 does not establish that an unrelated preserved daemon was unavailable; this is a conditional explanation, not proof of the reporter's exact ordering.
Generic inventory remains fail-closed. Exact-owner verification across daemon generations is separate work. This change does not solve a thrown stop, SSH loss of contact, unstamped identities, or all same-ID shutdown races. A missing diagnostics row remains insufficient evidence of process death.
Audits Terminal Completed Spawn Inputs README (`docs/audits/terminal-completed-spawn-inputs/README.md`)
Completed terminal spawns retain consumed inputs
Status: reproduced against actual TerminalHost, Session, output pipeline, and daemon admission code on Node 26.6.0 and installed Electron 43.7.0 / Node 24.21.0. The fix releases completed request objects and consumed history seed arrays while the terminal remains alive.
Retaining paths and fix
Three long-lived callbacks kept spawn-only input objects reachable:
terminal-host-session-create.ts::spawnAndPublishSessiongaveSessionan exit callback capturing the complete request and dependencies. A small factory now captures only the exit callback, session ID, and agent-session generation.TerminalHost.createOrAttachconstructed that exit callback beside the cancellation check that captures the request. Their shared lexical context kept the request reachable even after the first capture was projected. The unchanged exit/reap body now lives in a method bound to its host.session-output-pipeline.tscaptured pipeline options in its foreground-confirmation callback. Those options include history chunks thatSessionOutputPlanehas already consumed synchronously. The callback now captures the subprocess object; the liveness callback is also extracted before constructing the pipeline.
The subprocess remains the receiver of subprocess.confirmShellForeground?.(). The only production provider of the exit callback is TerminalHost; its bound method preserves the host receiver. Exit codes, incarnation tombstones, claimed-generation release, reaping, cancellation, and process ownership follow the same paths. A constructor-only maxTombstones field was removed to keep TerminalHost within the existing line limit; the registry receives the same configured/default value directly.
Production reachability and limits
daemon-provider-init.ts::initDaemonPtyProviderinstalls the local daemon adapter. The cold-restore path indaemon-pty-spawn-result.tssupplies recovered history to terminal creation.daemon-server.tsowns the host and admission objects;daemon-request-router.ts:59routescreateOrAttachto admission.daemon-terminal-admission.ts:90obtains inline history or takes completed transfer chunks, then passes the chunks, environment, and cancellation inputs into the host at line 96.session-output-plane.ts:63consumes all seed chunks into the emulator and retains the success flag.terminal-history-seed-transfer-registry.ts:97removes a completed transfer from its map and byte accounting when handing its chunks to creation. Its pending-transfer limits therefore do not bound the aggregate of already-consumed seeds retained by live sessions. The configured checkpoint maximum is 200,000,000 bytes, but these proofs use tiny seeds and do not measure a 200 MB allocation or incident-sized RSS.- Retention lasts for the live session. Disposal permits collection even before the fix. This is avoidable retention per live terminal, not proof of unlimited growth after successful teardown.
- Real admission stream callbacks still keep preparation/signal metadata while attached: the routed-session getter shares the admission context with its cancellation callback (
daemon-terminal-admission.ts:117–122). The admission control confirms those objects collect after publichost.detachwith the fix. This patch does not change that attached-stream lifetime. - Native
pty-subprocess/subprocess-handle.ts:48–60still captures its spawn arguments through the exit-status callback, including its merged environment object. Collection of the original request environment object does not prove all copied environment strings disappear from a real native process owner. The proof injects an inert subprocess and does not measure native allocations. - The daemon path can run locally and on execution hosts used remotely. No wire fields or messages change, and folder workspaces require no special behavior. The finding is compatible with a local application memory report such as #19831, but no affected-host process/heap evidence establishes that the incident used this restore path or that it explains the reported magnitude.
Reproduction and controls
spawn-source.cjs bundles actual source and reconstructs the baseline in memory by reversing fix.patch. SHA-256 checks fence both versions of all three changed modules using source-versions.json. The loader accepts the exact audit-branch pair and the exact independent-main publication pair; all other source hashes fail. Reports contain hashes of the source actually evaluated. Dependencies remain actual worktree code. Only the OS descendant-kill port is replaced with a throwing guard; subprocess handles are small injected objects, with no real shell, socket, process signal, or network activity.
reproduce.cjs measures weak references to request, environment, history array, and cancellation signal objects. It also tests pending ownership, actual exit/reap and claimed-generation replacement, retired-incarnation exit evidence, and foreground confirmation with the correct subprocess receiver and queued prompt delivery.
| Check | Baseline | Fixed |
|---|---|---|
| Three completed requests while three sessions remain live | 3 of each input object retained | 0 of each retained |
| One request during unresolved spawn | All four input objects retained | All four retained |
| That request after publication | All four retained | All four collectible |
| Inputs after disposal | All collectible | All collectible |
| Exit/reap, new incarnation/generation, shell confirmation | Pass | Pass |
admission-control.cjs exercises actual daemon admission and preparations above the actual host. A forwarding observer stores only weak references. Transport, attachment bookkeeping, and native subprocess ports are inert. Both runtimes reproduce the following:
| Admission phase | Original options/env/history | Preparation/signal | Request/payload |
|---|---|---|---|
| Baseline, attached or detached live session | Retained | Retained | Collectible |
| Fixed, attached live session | Collectible | Retained | Collectible |
| Fixed, detached live session | Collectible | Collectible | Collectible |
| Either version after disposal | Collectible | Collectible | Collectible |
The seeded snapshot remains readable after collection. These object reachability checks establish specific removed retaining paths; they do not establish total memory released. No heap-snapshot tool was exposed in this session. The historical Electron 43.4.1 binary was not tested. Each process uses a 192 MiB old-space limit and a 15-second deadline.
Run from the worktree:
ORCA_BACKGROUND_LAUNCH=1 node --expose-gc --max-old-space-size=192 docs/audits/terminal-completed-spawn-inputs/reproduce.cjs --baseline
ORCA_BACKGROUND_LAUNCH=1 node --expose-gc --max-old-space-size=192 docs/audits/terminal-completed-spawn-inputs/reproduce.cjs
ORCA_BACKGROUND_LAUNCH=1 node --expose-gc --max-old-space-size=192 docs/audits/terminal-completed-spawn-inputs/admission-control.cjs
ORCA_BACKGROUND_LAUNCH=1 node node_modules/vitest/vitest.mjs run --config config/vitest.config.ts src/main/daemon/terminal-host-spawn-input-retention.test.tsFor Electron, run the same proof scripts with the binary returned by require('electron'), the same Node flags, ELECTRON_RUN_AS_NODE=1, and ORCA_BACKGROUND_LAUNCH=1. This starts no application or window. Node and Electron reports are stored separately in this directory.
The four permanent regressions pass with the fix. The reconstructed baseline deliberately fails the two retention regressions and passes both lifecycle controls: ORCA_BACKGROUND_LAUNCH=1 node node_modules/vitest/vitest.mjs run --config docs/audits/terminal-completed-spawn-inputs/baseline.config.mjs exits 1. Existing host, concurrent create, teardown/recreate, reaping, agent ownership, preflight replacement, and history restore tests also pass: 67 tests across nine files. Node typecheck and the changed-code quality gate passed; explicit basic/type-aware lint includes the audit scripts.
Source identity and compatibility
source-versions.json records the exact audited branch baseline, fixed hashes, previously reviewed main commit 77cd61df396f25ec91ee2d5ddcbd1f55aa94f818, release v1.4.198 commit e0826956fcfc532f5a1e55b5e081f2e57e553c43, and independent publication main commit 291b4ddd6f1c1af480169885e0fda7f9c78ff053. The create and pipeline files exactly match these historical baselines. Historical TerminalHost differs only in the unrelated producer pause/resume source parameter from #20947 on the audit branch. This fix applies independently and does not require #20947.
The supported TerminalHost SHA-256 pairs are audit baseline 8cd6804b249ffb0d82da7f5a7ec8faa66514b0d6e3e36472cd81e6409c3edbc4 → fixed 23cfd9c6317db30edd48a0c8bd7c54658206654703461cf12295274b23fb8844, and independent-main baseline 5216562b3c10c5fce4238614dc3a81887c970359a60e7e23179c6dfa15ccbcca → fixed f22856504559a6bb78498c6d5aae07cbbd80a21019723278a560628e0142236f. Each selected fixed source is reverse-patched and checked against its own paired baseline hash.
The four permanent tests pass when the three patched main modules are overlaid on current dependencies. The six mapped-*.json reports repeat both runtime proofs and admission controls using the exact patched publication-main modules. They report the main hashes actually evaluated. This is a narrow compatibility check with working-tree dependencies, not a full historical application build. An optional ORCA_SPAWN_INPUT_PROOF_SOURCE_MAP points to a JSON object from these three relative source paths to exact reviewed fixed-source strings; unknown or incomplete mappings fail the same hash checks. With no mapping, the loader checks the published checkout directly. Mapped runs write separate reports prefixed mapped-.
Cancellation wait/listener findings from the preceding audit remain diagnostic and are outside this patch. The independent admission review narrowed the signal/environment claims before publication.
Audits Terminal Mode Tail Retention README (`docs/audits/terminal-mode-tail-retention/README.md`)
Retained terminal mode scan tails
The kitty keyboard tracker and daemon mouse-mode mirror retain an incomplete
escape-sequence tail of at most 4,096 UTF-16 code units. A V8 sliced string can
keep the entire consumed input alive through that small tail. An ordinary
split grouped mode sequence, ESC[?1049;2004;1000;, is enough: its 18-character
tail retains each input backing string while its parser stays idle.
The correction copies only accepted incomplete tails through the existingownRetainedString helper. Empty/rejected tails and short ESC/CSI prefixes keep
their existing behavior; the helper leaves strings shorter than 13 code units
alone. Parser state, live/replay semantics, stack caps, mode flags, and wire
content are unchanged. These are additional boundaries in
#20960, alongside the
PTY detector carries.
Reproduce
ORCA_BACKGROUND_LAUNCH=1 node --expose-gc --max-old-space-size=192 docs/audits/terminal-mode-tail-retention/reproduce.cjsRun the same script with the installed Electron executable, settingELECTRON_RUN_AS_NODE=1 and ORCA_BACKGROUND_LAUNCH=1, and passing the same
Node flags. This launches no app window or native PTY. Each run has a 30-second
deadline and writes either Node results or
Electron results.
The loader reads the actual five source modules, verifies their fixed hashes,
reverses only the three copy calls/imports in memory for the baseline, and
verifies the resulting baseline hashes. All evaluated module and bundle hashes
are recorded. It needs no Git history, absolute development paths, or ignored
notes. CRLF source text is normalized before hashing. The parsers and flag
parser match v1.4.198; all five modules match the pre-extension topic8d599520e44654a5c28e9930e3070c00d6499931, except for these copy calls. This
tests current dependencies and the selected source modules, not a historical
application binary. See source versions.
Each runtime checks 42 bounded heap cases: baseline, fixed Buffer copier, and
fixed Bufferless copier; kitty live/replay, mouse live; 32 distinct 64-Ki-character
inputs and eight 4-Mi-character inputs; short, complete, oversized, and C1-CSI
tail controls. Completion must reconstruct the correct modes and release the
large backing strings. Additional controls preserve replay push idempotence,
the 16-frame live stack cap, alternate-screen state, snapshot unknownness,
mouse encodings, and RIS with a trailing partial sequence.
Both runtimes pass all 42 cases. Representative live-path heap deltas in bytes:
| Runtime | Parser | Input × owners | Baseline | Buffer copy | Bufferless copy |
|---|---|---|---|---|---|
| Node 26 | Kitty | 64 Ki × 32 | 2,120,952 | 18,376 | 9,480 |
| Node 26 | Mouse | 64 Ki × 32 | 2,111,984 | 15,112 | 9,336 |
| Node 26 | Kitty | 4 Mi × 8 | 33,557,944 | 3,448 | 3,448 |
| Node 26 | Mouse | 4 Mi × 8 | 33,557,072 | 1,312 | 1,312 |
| Electron 43 / Node 24 | Kitty | 64 Ki × 32 | 2,111,864 | 12,244 | 5,192 |
| Electron 43 / Node 24 | Mouse | 64 Ki × 32 | 2,103,444 | 14,432 | 8,004 |
| Electron 43 / Node 24 | Kitty | 4 Mi × 8 | 33,556,316 | 1,884 | 2,604 |
| Electron 43 / Node 24 | Mouse | 4 Mi × 8 | 33,556,172 | 776 | 752 |
Heap readings include owner overhead and follow forced GC. The harness clears
V8's last successful regexp input identically in baseline and fixed cases to
isolate per-owner storage. That independent process-wide regexp reference can
keep a most-recent input alive until another successful match; this change does
not eliminate it. The Bufferless selection is memoized while Buffer is absent,
then Buffer is restored before measuring; it exercises the actual renderer
fallback without running a browser renderer.
Two permanent kitty heap regressions fail before the correction at 33,560,832
and 33,575,040 retained bytes against a 2-MiB limit. They also verify that the
retained prefix completes correctly and that replay/pop/snapshot state remains
valid. Existing parser and copier tests provide the wider protocol controls.
The two mouse regressions likewise fail before the correction at 33,559,240 and
33,573,200 bytes, and pass afterward with both CSI encodings. The five-suite
run passes 98 tests, including actual headless-emulator mode snapshots; Node
and renderer TypeScript checks pass.
Callers and lifetime
- Kitty renderer panes create or reuse one tracker per pane in
connect-pane-pty.ts:160.write-pty-output-to-xterm.ts:23feeds application
output;apply-reattach-payload.tsandhidden-output-seq-and-skip.tsfeed
replay. Fresh spawn and exit reset it, andterminal-pane-pane-closed.ts:69deletes the map entry. Dashboard previews
own another tracker per effect (AgentTerminalPreview.tsx:116); cleanup
removes its listeners and disposes its terminal. - Main's
orca-runtime-capture-provider-terminal-buffer.ts:23registers
temporary live scanners during provider snapshot acquisition and removes
them infinally. It creates a persistent tracker only after observing an
alternate-screen transition (:48–57).orca-runtime-on-pty-data.ts:30
feeds those trackers before later output processing. Exit, floating PTY
liveness cleanup, and provider generation reset delete the persistent entry. - The daemon does not directly instantiate the kitty tracker, despite its
old class comment: its kitty flags come from xterm. No mobile bundle imports
this class. Mobile can exercise main-side snapshot acquisition; SSH output
can reach main and renderer trackers through the existing provider routes. - Mouse mirrors are owned by
HeadlessEmulator(headless-emulator.ts:59).
Async writes scan after xterm parses the data (:190); synchronous live and
cold-restore writes scan at:224. Both daemon sessions and main's headless
projections use this emulator. It therefore also covers local/remote host
emulators serving mobile clients. Emulator disposal stops future writes;
eventual owner release removes the mirror. Completing/replacing its tail
also releases the old backing string. No ownership or shutdown rule changes.
Scope and limits
This is a per-owner last-input cost. It does not grow indefinitely with a fixed
set of parsers and bounded input chunks, and further output often completes or
replaces the tail. Multiple readers of the same input can share its backing
storage; do not sum their measurements as independent process memory.
Ordinary daemon bulk frames delivered to main are at most 64 Ki characters
(daemon-stream-data-batcher.ts:35), and ordinary relay output slices are
16 Ki characters (relay/pty-handler.ts:343). Mouse scanning inside the daemon
happens before outgoing stream framing. The 64-Ki cases demonstrate the issue
at a normal main-input bound; the 4-Mi cases amplify the mechanism, not a claim
that ordinary native reads or daemon frames have that size. Replay inputs and
transformed streams follow their own existing limits. No network, application
renderer, operating-system PTY, or incident heap was used in this proof.
This reduces retained output in local and SSH paths without changing published
terminal content. It neither establishes the trigger in #19831/#19768 nor
explains a reported sustained growth rate or multi-gigabyte incident by itself.
Audits Terminal Wait Leading Blank README (`docs/audits/terminal-wait-leading-blank/README.md`)
Terminal wait tail-window termination
startOfLastNonBlankLines loops indefinitely if its input begins with a newline and contains fewer nonblank rows than requested. Once the backward cursor reaches zero, JavaScript lastIndexOf clamps its negative start position to zero and rediscovers the same first newline. The cursor stops advancing.
The fix ends the scan when the cursor reaches zero and returns the existing short-tail offset, zero. It changes no prompt patterns or readiness rules. The ordinary finite-window selection cases retain their previous offsets.
Reproduce
ORCA_BACKGROUND_LAUNCH=1 node docs/audits/terminal-wait-leading-blank/reproduce.mjs > /tmp/orca-terminal-wait-leading-blank.jsonThe script bundles actual source and reverses only the two-line termination change for the baseline. Each case runs in an isolated child with a two-second deadline and 128 MiB heap limit. The parent confirms child termination; no app windows open. Five direct helper/detector cases time out before and return after. A sufficient-row control and actual headless terminal projection controls pass in both variants. Results include source hashes, platform, timing, and an explicit v1.4.198 helper comparison.
The regression suite uses a separate child for inputs that could hang the worker. It also tests exact row offsets with intervening whitespace, trailing blanks and tails shorter than the requested window. Fifty helper/detector tests pass.
Production and incident limits
The helper is byte-identical in v1.4.198. However, all inspected main/provider/renderer visible-screen projection routes pass through visibleNonBlankTerminalLines, and ordinary retained-tail construction removes blank rows too. The real headless producer control confirms this filtering. Calling the public detector directly with a leading newline is therefore insufficient evidence that those production routes trigger the defect.
A clipped 300-character preview can start at a newline. The preview fallback also preserves it when passed empty retained rows; the proof records both facts. It does not establish an actual application lifecycle that combines that fallback with a live detector call. That remains unproven.
This is a defensive termination fix found during the memory audit. The loop itself does not allocate a growing collection. No memory magnitude was measured, and it is not an attribution of #19768's main-process growth or #19831's application-scope OOM.
Audits Transcript Record Retention README (`docs/audits/transcript-record-retention/README.md`)
AI Vault oversized transcript records
The local/WSL incremental JSONL reader accumulated every chunk of a newline-free
record, then concatenated and decoded the whole record at newline or EOF. Its
piece list avoided quadratic copying but did not bound memory. Both the reader
and the scanner child's 384 MiB V8 old-space setting exist in v1.4.198; that
heap setting does not impose a 384 MiB process RSS cap on external buffers.
The fix shares the existing streamed remote reader's 10 MiB record limit and
checks local bytes before retaining, concatenating, or decoding an oversized
record. It rejects that session read through the existing scan-issue path; it
does not silently discard records or advance the persisted resume point after
failure. A legitimate record larger than 10 MiB is therefore unavailable in that
scan. Large files containing many smaller records continue to work. This covers
the resumable JSONL route, not every whole-document/import reader.
Reproduce
ORCA_BACKGROUND_LAUNCH=1 node --expose-gc docs/audits/transcript-record-retention/reproduce.mjsThe script writes a 64 MiB synthetic record in a temporary local file and bundles
the actual reader. Only the filesystem adapter is replaced with the equivalent
native createReadStream route plus a byte counter. The baseline removes the
three new budget checks in memory. Each case runs in its own Node process with--max-old-space-size=384; both terminated and unterminated records are tested.
Temporary files are removed. Results and bundle hashes were
captured on macOS with Node v26.6.0.
| Case | Bytes read | External allocation delta after read | Peak RSS |
|---|---|---|---|
| Before, unterminated | 67,108,864 | 201,303,426 | 248.25 MiB |
| After, unterminated | 10,551,296 | 10,528,130 | 62.13 MiB |
| Before, terminated | 67,108,865 | 201,368,963 | 248.22 MiB |
| After, terminated | 10,551,296 | 10,528,130 | 62.16 MiB |
RSS includes runtime overhead; allocator behavior varies. The mechanism is the
unbounded record assembly and its copies, not the precise sample. Ordinary
terminated records can be reclaimed afterward; this is a peak-allocation/OOM
risk, not evidence of a permanent leak after each read.
Validation: 71 tests across five reader/remote/cache/WSL suites passed, then ten
tests across the reader and cache-recovery suites passed (nine overlap). The
regressions cover all three assembly paths, exact byte boundaries, Unicode,
large multi-record files, iterator cleanup, and unchanged cached resume state
followed by successful repair. Full typecheck, final node typecheck, lint, and
changed-code quality passed. Tests used ORCA_BACKGROUND_LAUNCH=1.
This is another reachable allocation mechanism within the app process group.
#19831 does not establish an oversized transcript record. In the reported build
normal scanning runs in a child, so this route does not explain #19768's isolated
main-PID measurement. No affected-host attribution is claimed.
Audits Tui Transcript Acquisition README (`docs/audits/tui-transcript-acquisition/README.md`)
Transcript catchup can outlive host teardown
Host teardown stops TUI transcript catchup before draining in-flight handoffs. Previously, catchup setup registered its state only after asynchronous path resolution and stored its unsubscribe function only after asynchronous subscription acquisition. Teardown could miss either resource. A handoff that had not entered preparation yet could also start a watcher after stopAll. Actual-host tests reproduced a surviving watcher after the host session was removed. Stopping an already acquired watcher before its first snapshot instead left preparation waiting indefinitely for that snapshot.
Ownership fix
Catchup now registers its state before its first await and owns an abort controller throughout setup. It passes the existing resolver/subscriber cancellation signal, releases late subscriptions, settles the initial-ready wait on stop, and preserves a newer same-session acquisition. stopAll permanently closes this host's admission; ordinary per-session stop still permits a replacement.
Preparation returns its signal internally so the handoff checks cancellation immediately before and after launching a TUI. A dedicated internal cancellation error, while no TUI owner or process identity has been committed, releases the unused reservation through the existing fenced abandonStoredAgentSessionHandoffAttempt transition. If launch returns after cancellation with an owner, the existing proven cleanup path is invoked; if cleanup is unavailable or fails, ownership is retained and manual recovery remains required. It leaves a recoverable native lease without acquiring a replacement. This distinction matters: ordinary preparation failure invokes native recovery, and a delayed replacement acquisition can finish after the five-second teardown drain. Ordinary read failures retain that recovery behavior. Canceled recovery of a live TUI stops without retrying or relabeling its live lease, and settles any original durable operation that was still pending.
These are internal lifecycle changes. They add no wire type and infer no remote process death. A launch that remains in flight beyond the bounded teardown drain, and boundary/import I/O already admitted before cancellation, remain outside this change's cancellation guarantee.
Reproduce
ORCA_BACKGROUND_LAUNCH=1 node docs/audits/tui-transcript-acquisition/reproduce.mjsThe script runs seven tests through the actual host, handoff coordinator, durable record store, journal, and transcript watcher. Real file resolution, watcher installation, and initial read are paused at explicit asynchronous boundaries; provider processes use the existing fake adapter/transport. No real shell or app window launches.
fix.patch is reversed inside a temporary Vite transform for the baseline. The new internal error declaration remains available to the same assertions; it does not change baseline control flow. Source hashes and exact failing cases are recorded in results.json. Each runner uses a 512 MiB old-space limit, a 90-second deadline, and the repository's cross-platform runProcess. The proof requires the fixed runner to exit successfully in addition to matching its seven-pass/zero-fail report. Temporary runner/configuration files and acquired watchers are cleaned up.
| Version | Passed | Failed |
|---|---|---|
| Before fix | 1 | 6 |
| With fix | 7 | 0 |
The five preparation cases cover resolution, subscription return, initial snapshot, admission after teardown, and a completed preparation whose caller has not resumed. The recovery case preserves the live TUI lease. The control delays native acquisition after an ordinary resolver error and verifies the original error and recovery behavior. Six additional ownership tests cover overlapping prepare/recover replacements, per-session restart, repeated shutdown, and the signal returned when no supported record is available. Existing catchup tests preserve live appends and restart gap replay.
Two additional handoff regressions cover a TUI launch returning after cancellation and cancellation during recovery of a pending durable operation. Both fail against the original PR head and pass with the review fix. A late owner is stopped through the existing proven-cleanup contract, then the reservation is abandoned without acquiring a replacement native owner. If cleanup is unavailable or cannot prove the owner stopped, the existing manual-recovery path retains ownership instead. Recovery cancellation marks the original operation failed without relabeling the live TUI lease.
Version and attribution
Named-path reads confirm the same setup gaps, unguarded forward launch, and stop-before-drain ordering in v1.4.198. In that tag the teardown phases are inline in structured-agent-session-host.ts:254; current source extracts them into structured-agent-session-host-teardown.ts. The executable proof compares current source before/after this fix. It establishes an execution-host watcher retaining path present in the reported version, without proving that #19831 or #19768 exercised this teardown race or explaining either report's memory magnitude.
Audits Tui Transcript Close README (`docs/audits/tui-transcript-close/README.md`)
Closed TUI sessions retain transcript watchers
After a structured session hands off to a terminal, StructuredTuiTranscriptCatchup tails the provider transcript. Successful StructuredAgentSessionHost.close stopped the TUI owner, durably released its lease, and removed the host session, but did not stop its transcript catchup. The live watcher and catchup state, including the previously seen message IDs, remained reachable. Repeated closes of distinct sessions could accumulate these resources until host teardown.
The fix calls the existing stopTuiHistoryCatchup callback after the verified terminal close and durable lease transition succeed. It removes the catchup state and unsubscribes the watcher before later journal eviction. An unverified stop or failed lease write preserves the watcher for retry. A later journal-close failure cannot undo completed watcher cleanup. The execution host retains authority; no client-side inference of remote process death or wire change is involved.
Reproduce
From the repository root with existing dependencies installed:
ORCA_BACKGROUND_LAUNCH=1 node docs/audits/tui-transcript-close/reproduce.mjsThe proof uses the actual structured host, record store, journal, handoff coordinator, and transcript watcher against a temporary synthetic Codex transcript. Only provider process acquisition/stop is a test transport; no real shell or Orca window launches. It removes the single cleanup call in a temporary Vite transform for the baseline, then runs the same four tests against the fixed source. It uses the repository's runProcess and cleans temporary configurations/module files. Source hashes are recorded in results.json.
| Version | Passed | Failed |
|---|---|---|
| Before fix | 0 | 4 |
| With fix | 4 | 0 |
The successful-close case observes one added watcher, proves live TUI text reaches the journal, closes the session, verifies the lease is released and session removed, and expects the watcher count to return to its original value. Before the fix it stays one higher. The remaining cases exercise unverified terminal stop/retry, failed durable transition/retry, and failed journal eviction/retry. These are four checks of one cleanup omission.
Existing catchup tests also verify that live appends and recovery of writes made while the host was down remain intact. All seven targeted tests passed, as did the Node typecheck and direct lint.
Version and limits
Named-path reads of v1.4.198 confirm that its host close calls the same owner-close helper, that helper omits catchup cleanup, and its catchup owns the same state/watcher lifetime. The executable comparison uses current production source. This establishes a retaining path present in the reported build; it does not establish that either #19831 or #19768 exercised this handoff-and-close sequence, or measure either report's memory growth.
The separate asynchronous acquisition race remains open: teardown calls stopAll before draining handoffs, while catchup setup can still be awaiting path resolution or subscription acquisition. Merely rejecting a canceled preparation is insufficient as a full teardown fix: the forward handoff's existing failure recovery may acquire a native replacement, and the handoff drain has a five-second limit. That race needs its own owner-cancellation policy and regression proof; this change covers successful close of an acquired TUI owner.
Audits Working Directory Wait Retention README (`docs/audits/working-directory-wait-retention/README.md`)
Canceled cwd validation waiter lifetime
Canceled working-directory checks retained their AbortSignals while the shared native filesystem check remained pending. The change releases those caller references immediately while preserving the underlying native operation, raw-promise identity and callback ordering.
A small promise reaction and empty holder still remain per canceled wait until native settlement. JavaScript promise reactions cannot be removed. This correction releases the signal, listener and caller resolvers; it does not establish a total bound on waiting metadata or explain an incident's memory magnitude.
Ownership and callers
src/main/providers/working-directory-validation.ts keeps one pending validation per exact cwd. The raw fs.stat cannot be aborted, so the map entry and any UNC semaphore slot must survive caller cancellation until real settlement. Retiring them early would permit duplicate native work on the same stalled path.
Previously each caller registered a finally callback that captured its signal. The fix keeps each caller's raw promise reaction in its original position, but that reaction now references a small holder. Abort or settlement removes the abort listener and clears the holder. A separate waiter factory prevents the first signal from sharing the map's cleanup closure. The redundant per-call rejection observer is removed; the existing map-level then(forget, forget) still handles native failure when every caller has left.
The sole production importer is pty-subprocess/spawn-preflight.ts:127–136, through local-pty-utils.ts. daemon-terminal-admission.ts supplies a preparation signal, and pty-subprocess.ts forwards it to preflight. Ordinary daemon requests use a 30-second client timeout and a 5-second cancellation guard. A caller can therefore finish while the native filesystem operation remains pending across later requests. Those request timers do not bound the raw stat duration.
No-signal callers still receive the exact original promise. Native map deletion, UNC lane ownership, WSL checks, creation reservations, shutdown and process authority are unchanged. The change stays on the execution host and applies to folder workspaces and git worktrees without a wire change.
Why each raw reaction remains
Existing wait utilities were checked. A shared settlement observer changes this API's callback order: a raw-promise observer registered before a signal waiter can abort it before its raw result arrives. Moving every waiter behind an earlier shared observer would fulfill that waiter instead. The per-call holder preserves that order and synchronous cancellation. Six permanent regressions cover an external aborting observer before, between and after signal waiters, for native success and failure.
Before/after evidence
The standalone proof bundles the actual validation module, UNC path parser and semaphore. Its native async stat is a deferred fixture; WSL subprocess operations throw if unexpectedly reached. It performs no actual cwd probe, remote filesystem access, native subprocess launch or app launch. The fixture contains 32 small canceled callers per outcome; no large payload is attached.
| Before raw native settlement | Original Node 26.6.0 | Original Electron 43.7.0 / Node 24.21.0 | Fixed, both |
|---|---|---|---|
| Signals reachable | 32 | 32 | 0 |
| Cancellation errors reachable | 0 | 32 | 0 |
| Caller option objects reachable | 0 | 0 | 0 |
| Native stat calls | 1 | 1 | 1 |
All measured caller objects collect after native settlement in both versions. Both native success and failure have the same lifetime result. Other controls pass on both runtimes:
- Later live waiters receive the original operation's success or actionable error, and their listeners are removed.
- An already-aborted first caller still leaves the raw operation owned; no-signal callers share the same raw promise.
- Three canceled callers on one UNC host leave two native slots occupied. The third native operation starts only after one real completion.
- Forty-eight actual-module settlement/abort schedules and six raw-observer ordering cases match the original.
- Native rejection after all callers cancel produces no unhandled rejection.
- Synthetic CRLF source and patch reads produce identical reversed/fixed source and hashes without product writes.
sources.cjs reverses fix.patch in memory and checks exact baseline and fixed SHA-256 values. Source hashes use canonical LF; reports include effective dependency and bundle hashes. No git history, ignored notes or copied production implementation is needed to rerun the proof. Each run has a 20-second deadline; the commands below set a 192 MiB heap limit.
ORCA_BACKGROUND_LAUNCH=1 node --expose-gc --max-old-space-size=192 docs/audits/working-directory-wait-retention/reproduce.cjsFor Electron, run its installed executable with the same flags and script path, setting ELECTRON_RUN_AS_NODE=1 and ORCA_BACKGROUND_LAUNCH=1. It runs in Node mode and creates no windows.
Source compatibility and validation
The audited baseline module is byte-identical to main commit 291b4ddd6f1c1af480169885e0fda7f9c78ff053 and release v1.4.198 (e0826956fcfc532f5a1e55b5e081f2e57e553c43). All nine recorded caller/dependency sources also match that main commit. This one-product-file change does not depend on the shared waiter helper or its auth-wait changes. source-versions.json records the exact comparisons; historical source equality is not a historical packaged-runtime reproduction.
The fixed three-file regression run passed 31 tests. Reversing only this fix gives one expected first-caller retention failure and 30 passing controls, including the six raw-observer cases:
ORCA_BACKGROUND_LAUNCH=1 pnpm exec vitest run --config docs/audits/working-directory-wait-retention/before.config.mjs src/main/providers/working-directory-validation-retention.test.ts src/main/providers/working-directory-validation.test.ts src/main/daemon/pty-subprocess-cwd-cancel-identity.test.tsvalidation.json records verification results. The measured retention requires a still-pending native operation; no affected-host capture, byte slope or attribution to #19831 is claimed.
Bug Reproductions Native Chat Tool Pairing README (`docs/bug-reproductions/native-chat-tool-pairing/README.md`)
Native chat tool pairing
Latest follow-up: mobile row identity keeps an opened detail with its command after earlier rows disappear or move.
Long tool runs repeatedly scanned all unanswered calls to find each named result.
Calls that emitted no output stayed at the front of that scan. Positional results
also shifted the remaining pending-call array after every answer.
The shared matcher now uses a linked FIFO and builds an ID index only when a
named answer needs to match beyond its head. That index stays valid until the
pending queue empties. Named and unnamed answers remove a pending call without
array movement; each call enters the index at most once. Duplicate IDs retain
FIFO order. Existing pair limits and output references are preserved.
The implementation remains shared by desktop, mobile, and host readers.
The rendered check exposed related correctness defects: Claude and Codex
transcript decoders discarded result IDs, and folding used only call counts to
assign results. Decoders now retain the optional provider ID; folding uses
the canonical matcher and occurrence indices, so an unrelated result cannot
consume another call's answer. Unmatched named results stay visible separately
and cannot acquire a guessed owner during later folds. Results without IDs
remain positional; already imported calls missing IDs cannot own named results.
Legacy journal import also preserves IDs on singleton calls and keeps singleton
results as result messages. The focused importer/projector regression test
covers reverse batched results, mixed record shapes, duplicate IDs, anonymous
FIFO, and old persisted calls whose IDs were already lost. That integration
coverage is separate from the rendered transcript fixture below.
Reproduction and measurements
Baseline: 51e7181850b. At 2,048 calls, the regression tests measured 2,098,176
ID reads for reverse completions, 4,194,304 reads for silent-call backlogs and
unmatched results, and 2,096,128 moved array slots for FIFO completions. The
fixed implementation passes linear operation budgets at 64, 512, and 2,048
calls. Semantic differential tests preserve call/result references across
mixed IDs, interleavings, immutable inputs, and unusual limits.
Run the production benchmark with the original module on stdin:
git show 51e7181850b:src/shared/native-chat-tool-fold.ts |
ORCA_BACKGROUND_LAUNCH=1 node config/scripts/native-chat-tool-pairing-benchmark.mjsbenchmark.json records eight counterbalanced sample pairs
per workload, source bundle hashes, raw timings, and correctness checks. It
includes immediate pairs, reverse completion, positional FIFO, duplicate IDs,
silent-call backlogs, unmatched results, and finite limits at seven sizes.
At 2,048 calls, reverse completion improved from 7.46 ms to 0.163 ms, a
silent-call backlog from 13.02 ms to 0.244 ms, and immediate ordered pairs from
92.0 microseconds to 47.3 microseconds. Single-call backlog/missing workloads
remain within 11 ns of the baseline. These timings cover the shared pairing
function, not React, DOM, transport, or the full transcript projection. These
measurements identify the earlier matcher revision in the artifact. Later orphan
projection, journal import, and discarded-wrapper allocation changes are outside
this benchmark's scope.
The deterministic contract runs in pnpm test:perf:contracts. Broader coverage
uses the shared/native-chat and renderer/native-chat suites, provider transcript
reader suites, and tests/e2e/native-chat-tool-pairing.spec.ts for hidden
Electron rendering and output ownership.
Full folding, allocation, and mobile disclosure
The later allocation fix reuses the same matcher for folding attribution without
collecting pair wrappers that folding immediately discarded. Consumer pairing
still returns fresh wrappers with the original call/result references. No cache,
parallel matcher, or ownership fallback was added.
Run the three-version production-module benchmark and its retained-allocation
control with:
ORCA_BACKGROUND_LAUNCH=1 node --expose-gc config/scripts/native-chat-fold-benchmark.mjs --memory-control
ORCA_BACKGROUND_LAUNCH=1 node --expose-gc config/scripts/native-chat-fold-benchmark.mjsfull-fold-benchmark.json compares original51e7181850b, published b1e44269d655, and the measured worktree. It records
54 cases across three sizes, six workloads, and full folding, settled desktop
projection plus all-row render-data pairing, and repeated folding. Each
comparison uses six counterbalanced sample pairs; the table uses raw-sample
medians because the artifact's rounded summary loses microsecond precision.
The two comparisons have separate current samples.
| All-row projection and pairing | Original → current | Published → current |
|---|---|---|
| 8,192 reverse completions | 320.4727 → 3.7358 ms, about 86× | 3.5954 → 3.6126 ms |
| 8,192 silent calls plus 8,192 later answered calls | 558.9641 → 5.5075 ms, about 101× | 5.5551 → 5.2629 ms |
| 8,192 immediate ordered pairs | 2.4124 → 2.5636 ms | 2.6577 → 2.3606 ms |
These are large adversarial-run gains, not a general frame-time improvement.
At 32 immediate pairs, the original/current pipeline medians were 11.38 and
16.74 microseconds: roughly 5.4 microseconds of extra work. The separate
published/current comparison measured 15.60 and 13.10 microseconds. Original
folding and refolding alone were faster than the ownership-preserving fold:
for the large silent backlog, folding rose from 1.1191 to 2.6317 ms and refolding
from 0.3171 to 1.4090 ms. The expensive original consumer scan dominates the
large named-result cases. Full folding still visits the collection on updates;
settled row reuse does not make the fold incremental.
Against published C, the large fold medians fell approximately 3–9%; whole
pipeline changes varied, including the small reverse-case increase above.
The deterministic allocation gain is clearer: large silent-backlog folding
collects zero discarded pair wrappers instead of 16,384, while consumer pairing
retains the required 16,384. Its median pre-GC heap delta fell from 8.23 to
6.84 MB; retained output stayed near 0.23 MB. Named orphan conservation,
original block references, unique row IDs, and repeated-fold identity are
checked for published/current outputs, which are deeply equal for every
workload. Original output counts expose historical drops or guessed ownership;
those cases do not provide equivalent visible-output timing comparisons.
Preserving separately sourced orphan evidence still costs rows and memory.
With 8,192 singleton orphan outputs, current folding retains roughly 2.61 MB
versus the original's 0.15 MB; fresh all-open projection and pairing retain
about 9.23 MB versus 0.33 MB. Published/current retention is essentially the
same. The original grouped these outputs with unrelated calls. Desktop
windowing normally mounts only viewport rows, while this consumer benchmark
processes every run as opened. It excludes React commits, DOM layout, mobile
runtime, and transport latency.
full-fold-memory-control.json confirms that
the sampling method retains a live million-slot array at about 8 MB. Each
memory sample releases its preceding output, holds the current output through
forced GC, and excludes shared source inputs. Projection memory uses a fresh
projector/cache; timing reuses a settled projector. Pre-GC deltas indicate
allocation pressure, not peak memory or total allocated bytes. Negative
retained deltas are noise, not savings or per-update growth.
full-fold-benchmark-receipt.json records
commands, source/tool hashes, and the post-main-merge graph review. Only three
account-startup enum/map modules changed after measurement; those branches are
absent from the benchmark fixtures, so no new timing claim is made for them.
Mobile previously exposed only six tool rows with no way to reach later
outputs. The existing disclosure now reveals six more rows per request and can
collapse back to six. A closed run performs no pairing; an opened run collects
only the requested prefix plus one sentinel to decide whether Show more is
available. This limits wrapper and mounted-row retention, while matching may
still scan a pending suffix to find the requested owners. Long runs require
multiple reveals, and expanding them mounts more rows at the user's request.
Result-only summaries use the existing Result label without inventing a call
count. Local mobile stream and SSH-authoritative handler tests cover late named
completion, orphan reachability, cursor paging, and older-host no-cursor
replacement. They establish ownership and paging behavior, not network latency.
Rendered verification
The rebuilt Electron app passed the dedicated E2E test through the real Claude
JSONL reader: three reverse completions behind 32 silent calls, an unrelated
result, and successful/failed task updates. The inspected screenshot
shows all three outputs attached to their commands, the accepted pending task,
and the unrelated output under its own Result summary and row.
rendered-validation.json records the
commands, test source hash, exact worktree identity, hidden/unfocused native
window assertions, and process cleanup. The fixture uses synthetic Claude
JSONL through the production reader and real UI clicks. This is desktop
correctness evidence; it does not exercise an actual provider CLI or adoption
launch, measure mobile rendering, or measure SSH transport latency.
The mobile component check uses the actual React Native Web primitives and
production message/fold modules under the shipped browser build options and
shell content-security policy. The before capture
shows six orphan results with no way to reveal later rows. The after capture
shows all eight Result rows and the seventh output's full body. Real clicks on
Show more and Show less reveal and hide those rows. Further captures show
reversed named outputs and a separate orphan
and a named completion after seventeen silent calls.
mobile-rendered-validation.json records
row-specific full-body assertions, source and bundle hashes, the one-component
historical overlay, and exact headless-browser cleanup. This proves mobile web
component disclosure and ownership; it does not exercise a native phone, the
full session route, provider decoding, adoption, or transport.
The before screenshot shows the unrelated result and named outputs attached to silent calls, and the failed task replacing the accepted task. The before validation record identifies the three modules restored from 51e7181850b: native-chat-tool-fold.ts, transcript-record-blocks.ts, and transcript-line-decoders-codex.ts. Other app code, including the hidden launch policy, stayed current. This proves the original folding/decoder defect, rather than a full historical app build. Each of the three overlaid source files was restored byte for byte and the owned app process exited. Images are uploaded attachments, not committed binaries.
Bug Reproductions Opencode2 Form Created Kinds README (`docs/bug-reproductions/opencode2-form-created-kinds/README.md`)
OpenCode 2 `form.created` is not always a question (#22371)
OpenCode 2 has one form primitive and several producers. Orca's setup bridge mapped everyform.created to question.asked, which is Orca's un-evictable "the pane owner must answer
this" blocker — including forms owned by a sentinel that is not a session.
Captured against the shipped opencode v2.0.12 binary on macOS, driving the real TUI in a PTY
against a real opencode serve instance and reading the server's /api/event SSE stream.
The discriminator: `form.sessionID`, not `form.metadata.kind`
Form.Info is { id, sessionID, title, metadata?, fields }. Every Form.ask call site in the
v2.0.12 bundle (still exactly five on v2.0.15, in tool/plugin/question.ts,tool/plugin/websearch.ts and mcp/index.ts):
metadata.kind |
Form title | sessionID |
Blocks the pane owner |
|---|---|---|---|
question |
Questions |
the session | yes — the question tool |
websearch.provider |
Web Search / Choose a web search provider |
the session | yes — the turn is stalled |
mcp-elicitation |
<server> is requesting input |
"global" |
no — see below |
metadata.kind looks like the discriminator but cannot be one. In packages/schema/src/form.ts
on v2.0.15, Metadata is Schema.Record(Schema.String, Schema.Unknown) and line 130 declaresmetadata: Metadata.pipe(optional) — so metadata may be absent entirely and kind is a
convention no producer is obliged to stamp. The public POST /api/session/:sessionID/form
endpoint (packages/protocol/src/groups/session.ts:809, payload at ~147) lets any client raise a
genuinely blocking form on a real session with no metadata at all. Keying onmetadata.kind === "question" therefore drops real blockers silently.
What actually differs is the owner. mcp-elicitation passes GLOBAL_ELICITATION_SESSION_ID
("global", packages/core/src/mcp/index.ts:82), which is not a session, so a blocker minted for
it can never be retired by that session going idle — only by an exact form.replied /form.cancelled. websearch.provider passes the real context.sessionID, so session idle retires
it normally, and while it is pending the agent genuinely is waiting on the user.
So Orca blocks on every session-owned form and drops only the non-session sentinel. Upstream notes
in form.ts:122-129 that "global" is temporary and elicitations will get real session ids; when
that lands the exclusion stops matching and Orca starts blocking on them correctly.
form-created-question.json and form-replied-question.json are the live capture of thequestion tool's form being raised and answered. Note metadata.tool is { messageID, id },
not the { messageID, callID } that Orca's clearQuestionForToolPart matches on, and OpenCode 2
never emits message.part.updated at all — so that retirement path is dead for OpenCode 2 andform.replied / form.cancelled is the only reply-side retirement it has.
The reported menu does not raise a form
subagent-panel-screen.txt is the rendered PTY screen from the reported surface — theSubagents / Shell / Terminals activity dock, opened over a session with three subagents.
The whole time that dock was opened, paged and dismissed, the server's /api/event stream
carried nothing but server.connected, heartbeats, and unrelated skill.updated filewatcher
noise from another checkout. Same result for the shift+tab agent picker and the ctrl+p
command palette. The TUI's pickers are local Solid components; they never call Form.ask, so
they produce no server event of any kind and cannot be the thing Orca saw.
So this capture proves the mapping was wrong and which field fixes it; it does not reproduce
the exact frame in the issue screenshot.
Reproduce
opencode serve --hostname 127.0.0.1 --port 47391 # OPENCODE_SERVER_PASSWORD=<pw>
curl -s -u opencode:<pw> -N http://127.0.0.1:47391/api/event # tee this
opencode --server http://127.0.0.1:47391 --session <sid> # drive in a PTYAsk the agent to call its question tool for the question shape. For the picker and MCP
shapes, POST /api/session/<sid>/form with the metadata.kind from the table — that is the
same publish path the internal producers use, and it is what the plugin-level check was
verified against.
Bug Reproductions Opencode2 Session Lineage README (`docs/bug-reproductions/opencode2-session-lineage/README.md`)
Orca could not resolve OpenCode 2 session lineage (#22371 follow-up)
Orca's OpenCode plugin resolves a session's ancestry so a subagent's work rolls up to the pane
without taking it over. Against OpenCode 2 that resolution returned null for every session, so
every child session was published as if it were a root — and a subagent's question minted an
un-evictable "needs input" blocker the lead agent had never asked for.
Captured against the shipped opencode v2.0.12 binary on macOS: Orca's real generated plugin in~/.config/opencode/plugins/orca-opencode2-status.js, real ORCA_PANE_KEY / hook-token env, a
logging hook server on a loopback port, and opencode2 --standalone driven in a real PTY.
The SDK shape Orca was given
src/main/opencode2/status-plugin-setup-source.ts handed the shared lineage walk a client built
from the plugin setup() context. Probing that context live inside the running plugin:
| call | result |
|---|---|
ctx.session.get({ sessionID }, { signal }) |
the session record directly — no data wrapper |
ctx.session.get({ path: { id } }) |
throws Missing key at ["sessionID"] |
ctx.session.list |
undefined |
ctx.session.get.length |
1 |
lookupSessionList only accepts a result when result?.data?.id === sessionID, so every lookup
was discarded, and the session.list fallback it then tried does not exist. resolveRootSessionID
therefore returned null for every session and childState stayed permanently null.
The capture
One prompt, run twice, telling the agent to delegate echo hello-from-subagent to exactly one
subagent. hook-posts-before.jsonl and hook-posts-after.jsonl are the verbatim hook POSTs.
Before — the subagent publishes as a root:
{"hook_event_name": "SessionStart", "sessionID": "ses_f33016571ffenJDTLmPcLATavv"}
{"hook_event_name": "SessionBusy", "sessionID": "ses_f33016571ffenJDTLmPcLATavv"}
{"hook_event_name": "SessionBusy", "sessionID": "ses_f330152d1ffer9a1SCLERqCLv8"} <- the child
{"hook_event_name": "SessionBusy", "sessionID": "ses_f33016571ffenJDTLmPcLATavv"}ses_f330152d1ffer9a1SCLERqCLv8 never received a SessionStart because the plugin skipssession.created events that carry a parentID. OpenCode's own session_v2 row confirms it:parent_id = ses_f33016571ffenJDTLmPcLATavv.
After — the child is recognised and rolls up; only the root's id is ever published:
{"hook_event_name": "SessionStart", "sessionID": "ses_f32f26424ffe3xdiePHBUnsdAY"}
{"hook_event_name": "SessionBusy", "sessionID": "ses_f32f26424ffe3xdiePHBUnsdAY"}
{"hook_event_name": "SessionIdle", "sessionID": "ses_f32f26424ffe3xdiePHBUnsdAY"}The subagent still ran in the "after" capture (ses_f32f2519dffeNSA1jG2Z8JVJyv,parent_id = ses_f32f26424ffe3xdiePHBUnsdAY); its id simply never appears in a POST.
Reproduce
# Log POSTs on a free loopback port, then:
ORCA_PANE_KEY=tab:leaf ORCA_OPENCODE_AGENT=opencode2 \
ORCA_AGENT_HOOK_PORT=<port> ORCA_AGENT_HOOK_TOKEN=<token> \
opencode2 --standalone --prompt '<delegate something to one subagent>'Unset ORCA_AGENT_HOOK_ENDPOINT so the plugin reads the port/token from env instead of a live
Orca's endpoint file.
One trap: OpenCode 2 loads both orca-opencode-status.js and orca-opencode2-status.js from~/.config/opencode/plugins/, and both declare the same plugin id (orca-opencode-status).
Only one survives, and if it is the v1 file its agent gate silently returns no hooks, so nothing
posts at all. Move the v1 file aside while reproducing.
Bug Reproductions Usage Scan Append README (`docs/bug-reproductions/usage-scan-append/README.md`)
Usage scans after a transcript append
Latest follow-up: lossless source compression removes the disk increase in three measured histories, with CPU costs reported. Earlier tables below describe the uncompressed implementation.
Claude usage refresh previously cached a transcript only while its modification time and size were unchanged. Appending one usage row invalidated that cache and parsed every earlier user, tool and assistant row again. The production route is ClaudeUsageStore → UsageProviderStoreLifecycle.runScan → the usage worker → scanClaudeUsageFiles. Long tool output therefore made a small usage refresh expensive even when the number of usage records stayed fixed.
The regression is deterministic: the append test measures bytes returned by the real filesystem reader. Before the fix, a 424-byte append to a 1,083,929-byte transcript read 1,084,353 bytes. The new scan reads a verified suffix and a fixed amount of prefix evidence. It retains all usage history and places no new cap on transcripts, rows or tokens.
Reproduce
From the repository root:
ORCA_BACKGROUND_LAUNCH=1 pnpm test src/main/claude-usage src/main/usage/jsonl-line-offsets.test.ts
ORCA_BACKGROUND_LAUNCH=1 pnpm test src/main/codex-usage
ORCA_BACKGROUND_LAUNCH=1 ORCA_CLAUDE_USAGE_APPEND_BENCH_ROUNDS=8 ORCA_CLAUDE_USAGE_APPEND_BENCH_BASELINE=51e7181850b022bae2d41091fe1229accef4bd50 node config/scripts/claude-usage-append-benchmark.mjs --compare --verifyRun the timing benchmark while builds, test suites and other benchmarks are idle. It uses temporary transcript directories and loads the actual scanner through an esbuild bundle. Account/profile discovery is isolated; transcript reads remain real. The I/O wrapper records stream bytes and descriptor window reads without counting descriptor stream internals twice. Its accounting smoke check verifies one complete file read plus an eleven-byte descriptor read. Unaccounted readFile and readv APIs fail the benchmark.
The current comparison loader pins every project dependency to its named baseline commit and freezes the current source graph. The historical comparison below overlaid named Claude modules; the original scanner's five-module graph came entirely from the original commit. Fixtures are identical across arms. Arm order alternates across rounds; each round records scanner-cache-cold, unchanged, append and fresh-after-append phases. “Cold” describes the scanner cache: newly written fixtures can already be in the operating system page cache. Raw samples include read counts, bytes, scanner duration, cache serialization duration, serialized state bytes, source hashes and environment. Separate scanner and serialization values prevent a cursor from merely moving work into persistence.
The fixture set includes one thousand small transcripts; two and thirty-two MiB of UTF-8 user/tool output with the same 256 usage keys; and thirty-two MiB with 4,096 and 16,384 usage keys. Every appended result must match a fresh scan of the same bytes. Across baseline/current arms, additive checkpoint and generation metadata are excluded from behavioral comparisons; sessions, daily aggregates, per-file aggregates, line counts and owned keys are compared.
Initial controlled results
This comparison records the original append fix before the later protected source-sidecar and column-storage refinement. Its source hashes and raw samples remain unchanged; the later persistence measurements below use their own complete source graphs.
The final comparison.jsonl contains eight counterbalanced rounds per scenario on Node v24.20.0, macOS arm64, Apple M4 Max. Baseline is commit 51e7181850b022bae2d41091fe1229accef4bd50. Each arm ran first four times. No builds, tests, app or competing benchmark ran during the measurement. All eleven recorded current runtime-source hashes and four tool hashes were checked against the frozen worktree after the run. Exact within-arm cold parity, cross-arm behavioral parity and byte budgets passed.
The table shows medians. Time is the scanner plus compact cache serialization; the raw artifact records these separately and retains every unrounded sample.
| Tool payload / owned usage keys | Appended bytes | Before bytes read | After bytes read | Before time | After time | Before serialized cache | After serialized cache |
|---|---|---|---|---|---|---|---|
| 2 MiB / 256 | 4,730 | 2,288,956 | 29,306 | 5.943 ms | 0.616 ms | 8,196 B | 13,689 B |
| 32 MiB / 256 | 4,730 | 34,534,456 | 29,306 | 81.513 ms | 0.741 ms | 8,197 B | 13,691 B |
| 32 MiB / 4,096 | 4,748 | 36,560,492 | 29,324 | 92.323 ms | 3.093 ms | 114,262 B | 188,877 B |
| 32 MiB / 16,384 | 4,766 | 43,062,968 | 29,342 | 124.310 ms | 10.651 ms | 471,124 B | 766,924 B |
For the 256-key large transcript, scanner time alone fell from 81.494 ms to 0.724 ms, and append bytes read fell by 99.915%. Increasing transcript payload by 15.117× left appended reads unchanged. Increasing owned keys still increased scanner and serialization work, as expected. The cache is larger than the original aggregate-only cache because five old maxima must remain available to update a repeated historical key correctly.
The cold full-scan path also improved on these large fixtures: the 32-MiB/256-key scan took 82.270 ms before and 35.014 ms after, with sixteen KiB of extra evidence reads. The new raw-buffer line reader builds each complete line once, while retaining exact byte offsets. This timing includes the projection and checkpoint cost; it is not a separate parser microbenchmark.
The small-file tradeoff is visible. A thousand initial transcripts contain 459,780 bytes in total; appending a repeated maximum to each raises their total to 920,560 bytes. Full snapshot verification requires one additional read of each small file.
| Thousand-file phase | Before scan | After scan | Before bytes read | After bytes read |
|---|---|---|---|---|
| Cold | 73.233 ms | 94.872 ms | 459,780 | 919,560 |
| Unchanged | 9.176 ms | 9.400 ms | 0 | 0 |
| Changed | 71.938 ms | 94.933 ms | 920,560 | 1,841,120 |
Small-file cold scans cost about 21.6 ms more across this thousand-file fixture, a 29.5% increase. Unchanged refreshes retain zero transcript reads. This verification cost prevents a short, rotated or rewritten snapshot from being published under complete-file metadata; it is not hidden by excluding tiny files or adding a history cap. Scanner execution remains on the existing usage worker.
At 16,384 keys, the normalized cache is 766,924 bytes, compared with 6,203,025 bytes in the retained pre-normalization artifact: 87.6% fewer serialized bytes. The older run is evidence for the removed duplication, not a controlled timing comparison. Final scanner-plus-serialization time remains 10.651 ms at that key count, so the result does not claim constant total cost.
Shared reader before provider-specific projection
The durable byte reader and checkpoint evidence were extracted from Codex into src/main/usage/jsonl-file-checkpoint.ts and reused by both providers. The persisted Codex checkpoint fields remain compatible. The extraction also repairs a demonstrated race: path verification followed by a new path open could merge an old prefix with a replacement file, then publish the replacement's metadata over those old totals and reuse the result indefinitely.
Verification, initial stat, parsing and checkpoint creation now use one pinned descriptor. Parsing ends at the initial file size, so an append after the scan starts is picked up by the next refresh. Before publishing, the reader proves the complete requested byte range was consumed, checks the current path still names the opened generation, validates its original byte evidence and rejects truncation or changed snapshot evidence. Failed generations discard speculative projection and ownership before a cold retry. A short stream, digest window or legacy-prefix read on an unchanged snapshot propagates an error instead of publishing partial totals or retrying forever. Reopening requires positive generation or metadata change and closes each failed attempt once. Normal completion closes the owning handle; borrowed descriptor streams are not destroyed independently because that closes the descriptor on Node as well.
The inherited 12-KiB resumable-prefix crossover avoids paying more evidence work than a full read on tiny files. Tiny files hash the bytes actually parsed and verify them with one post-read descriptor read, including deliberately skipped legacy prefixes. They therefore read twice on a cold or changed scan, while unchanged files read zero bytes. Large cold scans add four 4-KiB evidence pages; a normal large append adds six pages. These costs are measured explicitly.
Claude projection and cache compatibility
Claude assistant rows are deduplicated by their existing request/message key. A repeated key keeps its first session, timestamp, model and location attribution, while each of the five token counters takes its independent maximum. The incremental projection applies only the token difference to the original session, day, model and location. It does not create another turn. A zero-cache-read turn is decremented once when that key's cache-read maximum changes from zero to positive. An invalid first timestamp remains invalid even when a later repeated row has a valid timestamp.
The persisted encounter order preserves cold-scan sorting when a token update creates a tie between locations, or a new row creates a timestamp tie between sessions. Keyless rows remain ordinary counted turns. Completed-line count is stored separately from the exposed line count: an unterminated fragment is counted once and read again after completion. A parseable unterminated usage row, including a duplicate that only raises maxima, suppresses resume until its provisional contribution can be parsed safely.
Cached cross-file ownership is retained while a changed owner is being read. Once that read validates, actual deleted or dropped keys are released and deferred forks reclaim them. A small owner's ordinary append therefore does not force every unchanged deferred fork to reread its history. Deletion, truncation and replacement that actually lose an owned key still recover that history from a surviving fork.
Generation metadata is stored independently of a resumable checkpoint, including for small files and parseable partial tails. Same-size replacement with a restored modification time is invalidated by physical identity; same-size in-place change with a restored modification time is invalidated by change time where available. Old cache entries lacking this metadata are cold-scanned once, then use unchanged zero-read reuse. Missing or unrecognized incremental state falls back to a full parse without discarding tracking preferences.
Resume state is validated at the boundary. It checks key coverage, compact tuple shape, finite token values, projection indexes and routing, and full session/location encounter-order coverage. Malformed state falls back to a cold scan instead of losing rows, double counting repeated keys or throwing during a correction.
Retained state and remaining scaling
The first implementation repeated each key and its full routing metadata in an owned-turn map. The isolated pre-normalization run measured 6,203,025 serialized bytes at 16,384 usage keys, with 4.306 ms spent serializing one appended scan. That exposed a second cost before finalizing the fix.
The normalized state reuses the existing ordered ownedDedupeKeys array. Parallel tuples store five maxima and a routing index; session/day/model/location routes are interned once. Existing-key updates retain their positions; new keys append. Tests include reversed suffix key order and JSON persistence between updates. Appends now use the tuples directly, replacing only corrected rows and sharing unchanged rows without mutating prior state. This removes the historical owned-turn object hydration and full tuple re-encoding pass.
Transcript-payload growth no longer determines append reads. Hydration, key validation, aggregate cloning and serialization still scale with the number of owned usage keys and rollups. The benchmark reports these costs rather than claiming constant total time or constant retained state. It also reports the small-file verification overhead rather than hiding it in a large-file average.
pre-normalization.jsonl retains the earlier same-source measurement that motivated the compact codec. It is not the final baseline/current timing comparison; source and method changes prevent treating those separate runs as controlled timing evidence.
Protected worker cache and visible report
The final implementation reuses upstream's worker-owned source sidecar and whenLoaded barrier. Per-file ownership, token maxima and checkpoint routes stay on the usage worker; main retains the session/day report. The disk codec packs each of six token columns as one scalar when all values match, or an exact dense column otherwise. It changes no worker or public usage-report shape, keeps every key and maximum, and serializes one file's columns at a time.
Unchanged refreshes retain the sidecar only after verified schema 7, clean normalization and identical file count, order and references. Any change, repair or legacy upgrade writes a new source generation. Report completion and report persistence still run.
Schema 7 source files and reports have separate SHA-256 guards over their exact JSON bytes. The source guard includes small files that have no resumable checkpoint. The worker verifies its source bytes before granting a request-local reuse proof; direct scanner callers and genuine older signed files still pass the coupled key/maxima/route/rollup validation. A malformed owner also invalidates deferred forks so the next scan can reclaim copied history. The checksum detects accidental drift; it does not authenticate a cache deliberately changed and re-sealed by another writer.
The report is authoritative for startup independently of the sidecar. A failed source write may leave an older sidecar, and a crash before report commit may leave a newer sidecar. Neither case replaces displayed report totals with another generation's totals. Legacy inline migration verifies protected files and reconstructs its report before splitting; invalid projections clear all visible totals and completion rather than presenting a partial history. An existing compatible newer sidecar survives a repeated migration after a failed report write.
Genuine unsigned schema 6 reports remain readable with their existing cache-trust policy until a successful source scan creates schema 7. Older signed tuple checkpoints migrate through their original coupled guard. Schema 7 requires valid integrity framing, including when a digest is removed. Downgrading to a schema 6 app causes a one-time cold rebuild while preserving tracking preferences.
Small reports keep upstream's synchronous load. Reports above its eight-MiB threshold use the existing worker operation; the verification fact is attached to the exact returned report text, so main does not hash those bytes again. Main still parses and serializes its visible rollups. If the worker is unavailable, whenLoaded covers the exceptional inline validation on main, including disabled cached history. Preference writes preserve that original inline source generation until a successful scan, and shutdown still drains writes after a failed preference write. This fallback can be slower, but does not retain the historical source graph in main.
Source-cache parsing, column decoding, key indexing, rollup cloning and durable sidecar writes still scale with cached history. The refinement moves this work off main and removes redundant representations; it does not claim constant total refresh time, bounded history, or universal detection of transcript rewrites.
Final Claude persistence measurements
The final evidence receipt verifies every captured production dependency, tool and artifact. Detailed costs and limits distinguish the original commit, published PR, merged upstream main and final worktree. The final timing drivers use six counterbalanced rounds with complete pinned source graphs and actual source-cache read/verification/decoding, scanning and durable source/report writes. Worker startup, IPC transport/queueing, telemetry, worktree discovery and rendering are excluded.
| Owned keys / phase | Published PR | Upstream main | Final |
|---|---|---|---|
| 16,384 repeated / cold | 72.455 ms | 61.117 ms | 83.160 ms |
| 16,384 repeated / unchanged | 13.106 ms | 21.844 ms | 12.290 ms |
| 16,384 repeated / append | 28.000 ms | 61.131 ms | 28.876 ms |
| 100,000 unique / cold | 385.203 ms | 265.264 ms | 390.682 ms |
| 100,000 unique / unchanged | 34.915 ms | 36.950 ms | 30.662 ms |
| 100,000 unique / append | 128.631 ms | 267.204 ms | 84.152 ms |
The 16k and 100k measurements retain every sample. Each unchanged final scan writes zero source-cache bytes, while still reading 471,838 or 6,126,038 source bytes and persisting a small report. At 100k, appended transcript reads fall from 47,323,259 to 25,030 bytes; including the source sidecar, total logical payload reads fall from approximately 50,303,147 to 6,151,068 bytes. These are measured operation costs, rather than whole-app latency or physical device I/O.
The four-arm byte check uses actual production layouts and indentation. At 16k repeated maxima, final report plus sources take 538,876 bytes: 29.8% below the published PR, 14.1% above main and 13.2% below original source. At 100k unique maxima, final state takes 6,128,046 bytes: 3.2% below the published PR, 105.5% above main and 57.8% above original source. One ownership-key list and the six dense token/routing columns dominate that adverse case; no duplicate key list remains. Dense cold rebuilding also remains slower than main, and the 16k complete append is slightly slower than the published PR despite a faster scanner.
The readiness diagnostic retains disabled history with zero per-source records on main. Upstream's report-only startup already removes the large source graph from main, so that improvement is inherited. First legacy migration becomes ready in 94.434 ms versus main's 63.060 ms. Exceptional validation yields 293 times, but its initial legacy parse still occupies main. A separate 13.87 MB report still requires a 16.512 ms observed main parse segment, and final serialization/checksum costs 20.628 ms versus main's 16.183 ms. These observations do not establish universal latency bounds.
Reproduce final evidence while other builds, tests, apps and benchmarks are idle:
ORCA_BACKGROUND_LAUNCH=1 node config/scripts/claude-usage-persistence-cost-benchmark.mjs --usage-keys 16384 --maxima-pattern repeated --rounds 6
ORCA_BACKGROUND_LAUNCH=1 node config/scripts/claude-usage-persistence-cost-benchmark.mjs --usage-keys 100000 --maxima-pattern unique --rounds 6
ORCA_BACKGROUND_LAUNCH=1 node config/scripts/claude-usage-readiness-cost-benchmark.mjs --usage-keys 100000 --large-report-mib 13 --rounds 6
ORCA_BACKGROUND_LAUNCH=1 node config/scripts/claude-usage-persisted-bytes-check.mjsEach tool accepts --output; the timing tools also accept --published-ref and --main-ref. Default immutable refs and complete provenance appear in the artifacts. Equivalent environment-variable launchers work on Windows. These macOS measurements make no Windows or SSH latency claim.
Codex parser retained memory
Codex folds each owned event into the existing session/day accumulator while reading. It keeps ownership keys and rollups, instead of an attributed-event array for the whole transcript. Claims are published only after descriptor validation; rejected reads discard their speculative rollups. Deferred records still advance cumulative token context.
Run the separate memory comparison while other benchmarks and builds are idle:
ORCA_BACKGROUND_LAUNCH=1 node config/scripts/codex-usage-parser-memory-benchmark.mjs --baseline-ref 51e7181850b022bae2d41091fe1229accef4bd50 --events 100000 --pairs 2 --sample-ms 35 --output docs/bug-reproductions/usage-scan-append/codex-parser-memory.jsonThe baseline bundles every project source dependency of the actual parser from the named commit; the current arm bundles and hashes the worktree dependency graph before starting workers. Each arm, scenario and round runs in a fresh Node process. Two rounds alternate baseline/current and current/baseline. The 100,000-record fixture has distinct timestamps and fixed token values. One scenario owns every record; the other defers 90% to a simulated prior owner. Both arms must preserve identical sessions, daily aggregates, owned keys and deferred state, with exact event and token totals.
Workers force two full garbage collections before parsing, every 35 ms during parsing, and after parsing with the returned result retained. Raw snapshots include heap, RSS, external buffers and elapsed time. The reported peak is the largest sampled retained heap increase; it does not measure absolute peak allocation or total process memory. Forced GC affects elapsed times, so this is a memory comparison rather than a speed benchmark. The artifact records runtime/platform details, fixture hash, all bundled source hashes, bundle hashes, instrumentation hashes, arm order and correctness counts.
The final codex-parser-memory.json comparison used Node 24.20.0 on darwin/arm64, an Apple M4 Max, and the 20,200,079-byte fixture. All eight fresh workers collected samples during parsing (3–12 per worker). The two counterbalanced runs measured these retained-heap increases:
| Owned records | Baseline sampled peak (MiB) | Current sampled peak (MiB) |
|---|---|---|
| All 100,000 | 44.681, 44.953 | 9.071, 7.964 |
| 10,000, with 90% deferred | 3.692, 3.746 | 1.233, 1.251 |
Every arm retained exactly 100,000 or 10,000 keys and counted events, with identical full usage-projection hashes and exact token totals. Final returned-result retention stayed approximately 6.49 → 6.51 MiB with all keys owned and 0.97 → 1.00 MiB with 90% deferred: the improvement removes intermediate event retention while preserving the result. Raw artifacts include all 16 baseline and 18 current source-module hashes; each current source and instrumentation hash matched the frozen worktree after the run. Snapshot records themselves add small duration-dependent observer overhead. A separate 1,000-record smoke passed parity but correctly flagged every measurement as final-only, so it is not used as peak-memory evidence.
codex-memory-exploration.json preserves the earlier actual-parser exploratory measurements: approximately 45.5 → 7.6 MiB sampled retained heap with all 100,000 keys owned, and 3.7 → 0.9 MiB with 10,000 keys owned. That earlier run overlaid only the baseline parser onto current dependencies and did not save source hashes or individual snapshots. Its limitations are explicit; the pinned full-graph comparison provides reproducible final evidence.
Correctness coverage and limits
The final validation record captures the frozen source hashes,
8,278 passing integrated tests across 839 files, 118 performance contracts,
full typechecking, changed-code quality, formatting and artifact hash checks.
The contract workflow is configured for Linux, macOS and Windows; this local run
used macOS. Rendered Electron evidence
records the rebuilt hidden app, output/task ownership and process cleanup.
Focused suites cover UTF-8 byte offsets, CRLF and tiny chunks; duplicate maxima and first metadata; keyless rows; malformed and parseable partial tails; restart after JSON persistence; location and session ties; worktree/folder attribution; owner deletion and key loss; truncation while opening; replacement and rewrite races; appends after the pinned stat; old-cache upgrade; malformed resume state; and stable short reads with single-handle cleanup and no partial ownership publication. An independent actual-source differential check ran 24 seeded append phases with 512 assistant rows, five sessions, three locations, three models, duplicate maxima, shifting metadata, invalid first dates and a JSON round trip before each resume. Sessions, daily aggregates and line counts matched cold scans after every phase.
Head and committed-boundary digests are evidence for the append-only contract, not a universal proof of every byte. Arbitrary middle-only rewrites that also grow a file while preserving the sampled windows can evade bounded evidence. Same-length dirty files take the full-parse path; changed same-length metadata during a read rejects the snapshot, and small-file validation hashes the full bytes. No new arbitrary history bound is used to conceal this limitation.
Site Content Docs Index (`docs/site/content/docs/index.mdx`)
title: What is Orca?
import { Callout } from '@/components/docs/prose'
Orca is a desktop IDE for running multiple AI coding agents side by side. Every task gets its own git worktree, its own agent terminal, and its own browser tab — so you can fan out work across Claude Code, Codex, Cursor CLI, and friends without stashing, branch-juggling, or losing flow.
When to use Orca
- You want three agents trying the same bug in parallel and to pick the winner.
- You want to review AI-generated diffs seriously before you ship them.
- You already pay for Claude Code, Codex, or Cursor CLI and want one place to orchestrate them.
- You want agents to run remotely — over SSH, on a self-hosted Orca server, or in an on-demand VM — without giving up your IDE.
Who it's for
Orca is designed for people who already write code for a living and want to use AI as leverage — not as a replacement. It assumes you read diffs, care about commits, and keep a worktree tidy. If you're looking for a no-code tool, Orca is not that.
What Orca is not
- Not a model. Orca runs agents you already use — bring your own Claude, Codex, or OpenCode subscription.
- Not a git replacement. Every worktree is a real git worktree. You can
cdin and use plain git whenever you want. - Not a hosted VPS product. Orca runs on your desktop by default. Remote compute uses machines and cloud accounts you control — SSH targets, self-hosted Orca servers, or Cloud VMs / per-workspace environments.
Site Content Docs Browser Overview (`docs/site/content/docs/browser/overview.mdx`)
title: Per-worktree browser
import { ImagePlaceholder } from '@/components/docs/prose'
Every Orca worktree has its own browser. It's a real Chromium window — address bar, history, devtools — embedded in a pane. Tabs are scoped to the worktree, so the app you're building against stays out of the way of your other work.
Controls
- Address bar with history and fuzzy URL completion. Non-URL text searches with your Default Search Engine — the same engine the new-tab omnibox uses. Prefix a query with
?in the + field to force search. - Back / forward / reload / stop. Hover the reload control for the normal-reload shortcut; right-click (or long-press) for Reload and Hard Reload with their shortcuts — hard reload bypasses the cache when you're iterating on local frontend assets.
Cmd-F— find in page.Cmd-T— new tab, scoped to this worktree.Cmd-Shift-T— reopen last closed tab.- Page links that use
target=_blankand ordinary unnamed popups open in new Orca tabs, using the opener tab's browser profile. Named or OAuth-style popups can still open in a separate window.
Worktree scoping
Tabs are filtered per worktree. Switching worktrees restores that worktree's browser tabs and scroll positions.
Remote workspaces
For a workspace on a paired Remote Orca Server, new browser pages render on this desktop by default while HTTP(S), WebSocket, DNS, and loopback traffic still go through the remote host. Input, selection, and popups stay native to this device without changing the page's network identity. The host indicator in the browser toolbar shows where traffic is going.
Under Settings → Browser → Remote server workspaces, choose This device or Server (streamed). The choice applies to new pages only; it does not move a page that is already open. If a locally rendered remote page cannot start, Reopen on server opens a new server-hosted page at its last address (signed-in or form state may differ).
For SSH workspaces, Browse through SSH workspace hosts controls whether browser traffic and DNS go through the workspace's SSH host or this device. Downloads from a client-hosted remote page stay on the remote workspace host, and uploads are staged there before the page receives them.
Link routing
Under Settings → Browser → Link Routing, choose whether http(s) links from the terminal, markdown, and editor open in Orca's per-worktree browser or the system browser.
A nested Hold Shift… toggle inverts that default for one click with the platform modifier (⇧⌘-click on macOS, Shift+Ctrl+click on Windows / Linux):
- When links open in Orca, the modifier sends one link to the system browser.
- When links open in the system browser, turn the toggle on so the modifier opens one link in Orca's built-in browser instead (plain click stays system).
A capability-verified paired runtime can offer the same Orca/system-browser choice for its terminal links. SSH-owned terminal links and links from older runtimes remain system-browser only. See Terminal → Link actions.
Downloads
Browser downloads appear in a shelf under the toolbar while they are active or recently completed, with actions to cancel an in-progress download, open a finished file, show it in its folder, or dismiss the row.
Share as artifact
Local HTML files opened in the worktree browser can use Share as artifact in the toolbar to mint a public view link through your signed-in Orca account (same opt-in gate and management surface as Markdown). Relative HTML assets are not uploaded — share a self-contained file or use absolute asset URLs. See Settings → Artifacts and CLI reference → Artifacts.
Viewport-size emulation
Set a custom viewport size on a browser tab to test responsive layouts without resizing the whole pane. Orca uses Chrome DevTools Protocol device emulation under the hood, so the page sees the emulated dimensions in window.innerWidth and media queries.
Automation
The browser is also scriptable by agents via the Orca CLI — orca snapshot, orca click, orca fill, and so on. Same browser you interact with, same tabs.
Next steps
- Design Mode — turn the browser into a pointer-to-code feedback loop.
- Browser-use profiles — run the browser with a specific login or cookie jar.
Site Content Docs Cli Overview (`docs/site/content/docs/cli/overview.mdx`)
title: Orca CLI overview
keywords:
- orca cli
- Orca CLI
- orca command line
- Orca skills
- npx skills add
- agent CLI
import { Callout } from '@/components/docs/prose'
The Orca CLI is the orca command-line interface for scripting a running Orca editor from any shell. Use it to create and inspect worktrees, drive agent terminals, open files and diffs, automate the built-in browser, run scheduled automations, share HTML/Markdown artifacts, and control Orca-native tools from scripts or AI agents.
It ships with the desktop app; register it under Settings → General → Orca CLI. On Linux the command is orca-ide, because GNOME Orca's screen reader already owns /usr/bin/orca — see Install → Linux.
Agents can install the matching Orca CLI skill with:
npx skills add https://github.com/stablyai/orca --skill orca-cli
# headless / no Settings UI:
orca skills install --skill orca-cliSee Skills registry & MCP for every installable Orca skill, including orca skills install / orca skills update.
Install & verify
command -v orca
orca status --jsonWorktree commands
orca worktree ps --json
orca worktree create --repo id:<repoId> --name my-task --issue 123 --json
orca worktree create --repo id:<repoId> --name review-pr-123 --pr 123 --json
orca worktree current --json
orca worktree set --worktree active --comment "reproduced bug" --json
orca worktree set --worktree active --gitlab-issue 42 --gitlab-mr 77 --json
orca worktree set --worktree active --pr null --json
orca worktree rm --worktree id:<id> --force --jsonFor selectors, setup flags, parent/child worktrees, and the broader command map, see Orca CLI reference.
Terminal commands
orca terminal list --json
orca terminal read --json
orca terminal send --text "continue" --enter --json
orca terminal wait --for tui-idle --timeout-ms 30000 --json
orca terminal create --worktree path:/projects/app --command "npm test" --json
orca terminal split --direction vertical --command "npm run dev" --jsonFor tracked multi-agent work, use Orchestration instead of plain terminal prompts.
File commands
Open files and diffs as tabs in their Orca worktree from a shell. They open without changing what you are looking at; add --focus to be brought to the file:
orca file open src/App.tsx
orca file diff src/App.tsx --staged
orca file open-changed --mode bothUse --worktree <selector> when the shell's current directory is not inside the target worktree.
Browser profiles
Browser profiles isolate tab session state, so a script or agent can test with different cookies, local storage, and logged-in identities. The CLI exposes profile commands under orca tab profile; start with orca tab profile list --json, then use create, set, clone, or use-default as needed.
Scheduled automations
Use orca automations to create, inspect, run, and remove scheduled Orca tasks from a shell. Start with Scheduled automations when you want a recurring prompt to run against a repo or an existing worktree.
Artifacts
Share HTML or Markdown as public view links through the signed-in Orca account (orca artifacts share|update|list|delete). Publishing is opt-in under Settings → Artifacts. Command details: CLI reference → Artifacts.
Browser automation
The CLI also drives the built-in browser with a snapshot-interact-re-snapshot loop:
orca goto --url https://example.com --json
orca snapshot --json # returns refs like @e1, @e3
orca click --element @e3 --json
orca fill --element @e1 --value "[email protected]" --json
orca screenshot --jsonFor responsive browser checks, switch the active tab to a named device profile:
orca set device --name "iPhone 12" --json
orca screenshot --jsonMobile emulator
The CLI can also drive an iOS Simulator through Orca's mobile emulator bridge. It is scoped to the active worktree, so agents and scripts can attach a simulator from orca emulator list, tap normalized coordinates, type text, send gestures, rotate the device, and shut it down without leaving Orca.
orca emulator list --json
orca emulator attach "<device-name-or-udid>" --json
orca emulator tap 0.5 0.7 --json
orca emulator type "hello" --json
orca emulator gesture '[{"type":"begin","x":0.5,"y":0.8},{"type":"move","x":0.5,"y":0.4},{"type":"end","x":0.5,"y":0.2}]' --json
orca emulator rotate landscape_left --json
orca emulator kill --jsonUse --worktree <selector>, --device , or --emulator when a script needs an explicit target.
STYLEGUIDE (`docs/STYLEGUIDE.md`)
Orca UI Style Guide
This is the UI/visual design doc for Orca — color tokens, typography, component selection, and UX rules. It is not an architecture doc; for system-level design see code and inline comments. Token values live in src/renderer/src/assets/main.css (canonical); this file documents the roles and rules for using them.
Overview
Orca is an Electron desktop app for orchestrating coding agents across git worktrees. The visual identity is monochrome and quiet — neutral grays carry the chrome, color is reserved for state (selection ring, destructive, git decorations). The product spends most of its time hosting other people's tools (Monaco, xterm, Markdown previews), so Orca's own UI should recede and frame.
When in doubt:
- Reach for muted/accent/border before reaching for color.
- Reach for CSS variables before hardcoding hex.
- Match the nearest shadcn primitive before writing custom CSS.
Source of truth
| Concern | Canonical location |
|---|---|
| Color tokens | src/renderer/src/assets/main.css (:root, .dark) |
| Tailwind theme bindings | Same file, @theme inline { … } block |
| Component primitives | src/renderer/src/components/ui/ (shadcn-style) |
| App typography / scrollbars / titlebar chrome | Same main.css |
Never hardcode a hex value in component code if a variable already covers it. If a new token is needed, add it to main.css (both :root and .dark), expose it in the @theme inline block, then use it.
Color roles
Tokens come in pairs: a surface and a foreground that meets contrast on it. Always use them together.
| Role | Use it for | Don't use it for |
|---|---|---|
background / foreground |
App canvas, default text | Cards, popovers, sidebar (have their own) |
card / card-foreground |
Panels lifted off the canvas | The canvas itself |
popover / popover-foreground |
Floating menus, dropdowns, hovercards | Inline UI |
primary / primary-foreground |
The single affirmative action in a flow (Save, Confirm) | Decorative accents; hover states; secondary actions |
secondary / secondary-foreground |
Lower-emphasis actions next to a primary | The affirmative action |
muted / muted-foreground |
De-emphasized text, captions, placeholders, disabled chrome | Body copy; primary actions |
accent / accent-foreground |
Hover/active backgrounds for ghost buttons and list rows | Solid filled buttons (use secondary instead) |
destructive / destructive-foreground |
Delete, discard, irreversible-action buttons; error states | Cancel buttons (Cancel is not destructive) |
border |
All hairlines: dividers, input outlines, card edges | Heavy emphasis; that's ring |
input |
Form field background only | Anywhere outside form fields |
ring |
Focus-visible outlines, active selection halos | Persistent decoration |
sidebar (+ variants) |
The worktree sidebar and its children | Other panels |
editor-surface |
Background of Monaco / markdown editor panes | App chrome |
The sidebar family expands into --sidebar, --sidebar-foreground, --sidebar-primary, --sidebar-primary-foreground, --sidebar-accent, --sidebar-accent-foreground, --sidebar-border, and --sidebar-ring — use them inside the worktree sidebar so its hover/selected/focus states stay consistent and don't bleed into other panels. editor-surface is its own token (not just background) because Monaco and the markdown editor have a slightly darker surface in dark mode to match VS Code conventions; reach for it whenever you're rendering an editor pane.
Git decoration colors
For diff status, file-tree decorations, and the changes view, use the git decoration tokens (mirroring VS Code's palette so users transferring from VS Code aren't surprised):
| Token | State |
|---|---|
--git-decoration-added |
Added / new |
--git-decoration-modified |
Modified |
--git-decoration-deleted |
Deleted |
--git-decoration-renamed |
Renamed |
--git-decoration-untracked |
Untracked |
--git-decoration-copied |
Copied |
--git-decoration-ignored |
Ignored by git |
Use these only for git status. Don't reuse them for unrelated state colors — that breaks the convention.
List rows: hover, selected, current
A common point of drift. Use these conventions for any list-style row (worktrees, command palette items, settings nav):
- Idle: transparent background.
- Hover:
bg-accent(in the worktree sidebar,bg-sidebar-accent). - Keyboard-selected (cmdk highlight): do not rely on flat
bg-accentalone on light popover/dialog surfaces —--accent(#f5f5f5) is nearly identical to--background(#fff), so the cursor vanishes. Use the jump-palette recipe inmain.css(.jump-palette-item[data-selected='true']):color-mixforeground into background (~12%) plus an inset ring. Expose the mix as--jump-palette-selection-surfacewhen nested cutouts (status pips) must match. Thedata-selectedattribute is set bycmdkautomatically. - Persistent "current" / "active" row (e.g. the worktree the user is viewing): also
bg-accent, plus adata-current="true"attribute so CSS or future styling can distinguish it from the cmdk highlight. - Don't: hardcode
bg-[#ededed]/bg-[#333333]or invent a "selected" color. Mix from existing tokens (foreground/background/accent) so light/dark stay aligned.
Color mixing
When you need a tint (e.g. a 12% primary wash on hover), use color-mix against the existing token, not a new hex:
background: color-mix(in srgb, var(--primary) 12%, var(--background));This keeps light/dark parity automatic.
Typography
- Family:
Geistis loaded as a single variable woff2 (weight range 100–900). Always reach forGeistfor sans, neverInteror system sans. - Mono:
var(--font-mono)— used for paths, terminal-adjacent UI, code, and anywhere monospace conveys "this is literal." - Body letter-spacing:
0.01em(set globally onbody). Don't override per component. - Sizes: Tailwind's default scale. Common sizes in this repo:
- 11px (uppercase meta, sidebar headers, captions) — pair with
font-weight: 600andtext-transform: uppercaseandletter-spacing: 0.05emfor category labels. - 12px (sub-text, paths, secondary content)
- 13px (sidebar items, dense list rows)
- 14px (default body, button text in
defaultsize)
- 11px (uppercase meta, sidebar headers, captions) — pair with
- Named small steps:
text-2xs(11px) andtext-3xs(10px) are theme steps inmain.css. Use them instead oftext-[11px]/text-[10px].
Chat type scale
The native chat uses four steps and no others. Never write an arbitrary size (text-[13px]) inside components/native-chat/. pnpm lint enforces this, along with the softened-foreground and focus-ring rules below (config/oxlint-plugins/native-chat-design-tokens.mjs).
| Step | Default | Use it for |
|---|---|---|
text-sm |
14px | Anything the user reads: message prose, tool and agent activity rows, card titles and bodies, the composer |
text-xs |
12px | Labels, timestamps, counters, secondary lines under a row |
text-2xs |
11px | Captions, attribution, uppercase category labels |
text-3xs |
10px | Badges and mono pills |
text-chat-code |
12px | Code blocks, diffs, commands and tool output |
The chat root (.native-chat-appearance) redefines these steps as ratios of the Text size setting, so every tier scales together; text-chat-code follows the Code text size setting. The chat root also ties the spacing unit to the text size (4px at the default 14px), so icons, gaps, padding and row heights grow with the text. Use spacing-scale utilities (size-3.5, min-h-6.5, pl-3.25), not pixel lengths (min-h-[26px]), or the element stays fixed while the rest scales. Size an element with one of these utilities only: a CSS rule that sets font-size outside a layer silently overrides them.
Chat text colour, hover and focus
- Primary text on the chat canvas uses
text-chat-foreground, andtext-chat-foreground-strongfor titles and emphasis; both follow the Contrast setting. Don't softentext-foregroundby hand (text-foreground/85).text-foregroundis for text on a lifted surface inside the chat. - Secondary text on the chat canvas (transcript rows, notices, labels between messages) uses
text-chat-foreground-faint.text-muted-foregroundis for text on a lifted surface inside the chat: cards, menus, popovers and the docks above the composer. - Row hover in the transcript is
hover:bg-accent/20. Buttons, menu items and card controls usehover:bg-accent. - Focus rings: full-width transcript rows use the inset ring (
focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-ring) so it isn't clipped by the scroll container; standalone controls usefocus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring. Inside the chat--ringis--chat-focus-ring, which clears 3:1 on the canvas; don't lower its opacity. - State colours in the chat: warnings use
text-chat-warning; diff lines usetext-chat-diff-added/text-chat-diff-removed; what the reader has not seen yet usestext-chat-unread. They are mixed toward the foreground so they pass text contrast in light and dark. Don't use raw palette colours (text-yellow-500) or the git decoration tokens for text on a tinted row.
Radius
--radius: 0.625rem (10px) is the base; the rest are computed (--radius-sm = 0.6×, --radius-md = 0.8×, --radius-lg = 1×, --radius-xl = 1.4×, etc.). Buttons and inputs use rounded-md; the Card primitive uses rounded-xl; badges use rounded-full. Match the existing primitive's radius rather than introducing a new one.
Elevation & shadows
Orca uses shadows sparingly. Three levels in practice:
- Inset hairline —
border+bordertoken. The default. Almost everything sits at this level. - Subtle lift —
shadow-xs+ a single-token border. Outline buttons, embedded cards. - Floating —
shadow-floating(0 10px 24px rgba(0, 0, 0, 0.18)). Popovers, popups that escape the editor surface. Reserved.
Don't add a fourth level. If something needs more emphasis than "floating," you're probably reaching for the focus ring instead.
Components
Use the shadcn primitives in src/renderer/src/components/ui/ before writing anything custom. The shadcn-style wrappers in this folder follow a consistent pattern:
- Most carry a
data-slot="<name>"attribute on their root for CSS targeting — do not strip it. (The non-shadcn helpers in this folder —sonnerandrepo-multi-combobox— don't follow this pattern and shouldn't be modeled when adding new primitives that should.) - Use
cn()for class merging. Pass userclassNamelast so callers can override. - Use
class-variance-authority(CVA) for variants when there are multiple.
Buttons (`button.tsx`)
Variants in priority order:
| Variant | Use case |
|---|---|
default |
The single affirmative action in a flow. |
secondary |
Lower-emphasis sibling next to a default. |
outline |
Toolbar / standalone actions where a filled button feels heavy. |
ghost |
Icon buttons, list-row triggers, anywhere chrome should disappear. |
link |
Inline text actions inside paragraphs. |
destructive |
Delete, discard, irreversible. Never for Cancel. |
Sizes: default (36px), sm (32px), xs (24px), lg (40px), plus icon, icon-xs, icon-sm, icon-lg. Match the size to the surrounding row height — don't drop a default button into a 28px toolbar.
Other primitives in this repo
Browse src/renderer/src/components/ui/ for the full list. Most wrap a Radix UI primitive — exceptions are command (wraps cmdk), sonner (wraps sonner), and the visual-only wrappers (badge, button-group, card, input) which apply tokens and Tailwind utilities directly. Never reimplement headless behavior; extend the existing wrapper.
Use DialogContent surface="security" for every dialog that asks the user to trust, approve, run or remove code, including plugin provenance and approval, so plugin themes cannot change the trust surface's tokens. Use its scrollable prop when the whole dialog scrolls. Wrap a trust decision outside a dialog, such as an agent approval card or a certificate override, in SecuritySurface.
Picking the right primitive
When a control has multiple plausible primitives, use this fork:
| You want… | Reach for | Don't use |
|---|---|---|
| Hover-only label on an icon-only button | Tooltip |
HoverCard (too heavy), title attr |
| Hover preview of richer content (avatar + summary) | HoverCard |
Tooltip (no rich content) |
| Click-revealed menu with actions | DropdownMenu |
Popover with hand-rolled list |
| Right-click contextual actions | ContextMenu |
DropdownMenu (different invocation) |
| Click-revealed surface with arbitrary content (form, picker) | Popover |
Dialog (it traps focus and dims) |
| Modal that demands a decision before you continue | Dialog |
Popover, inline overlay |
| Drawer / panel sliding in from an edge | Sheet |
Dialog centered |
| Single choice from a known list | Select |
Custom listbox |
| Single choice with search / fuzzy filtering | Command inside Popover |
Select (no search) |
| Multi-select with search | repo-multi-combobox (mirror its pattern) |
Roll a new one |
| Transient confirmation ("Saved", "Copied") | sonner toast |
Dialog, inline banner |
| Persistent inline status ("3 errors") | inline text + Badge |
toast (toasts disappear) |
If you find yourself styling around a primitive (`` to act like a <Dialog>, or vice versa), stop and reconsider — the focus-management semantics differ and a future contributor will be misled by the mismatch.
Tooltips
Tooltips exist to name a control whose meaning isn't obvious from its appearance. They are not the place to teach, persuade, or warn — anything users need to read while acting belongs in the visible UI.
- Use a tooltip when: an icon-only button or compact chip needs a label. Toolbar icons, badges with abbreviations, truncated paths.
- Don't use a tooltip when: the control already has a visible label, the content is interactive (links, buttons), or the message is critical (errors, blocking warnings — those go inline).
- Mounting: the global
<TooltipProvider delayDuration={400}>lives at the App root. Don't nest a secondTooltipProviderunless you need a different delay for a tightly-scoped surface. - Trigger pattern: wrap the trigger element with
<TooltipTrigger asChild>so the tooltip's accessibility props attach to the button (not a wrapper span). This is required for keyboard focus to surface the tooltip. - Placement: default
side="top" sideOffset={4}— match the toolbar pattern insidebar/SidebarToolbar.tsx. Pick a different side only when the default would clip against the viewport. - Shortcut chips inside tooltips: if the action has a keyboard shortcut, append
<ShortcutKeyCombo />rather than baking the keys into the label string. The chips render correctly per platform; baked-in strings drift.
<Tooltip>
<TooltipTrigger asChild>
<Button variant="ghost" size="icon-sm" onClick={openSettings}>
<Settings />
</Button>
</TooltipTrigger>
<TooltipContent side="top" sideOffset={4}>
Settings
</TooltipContent>
</Tooltip>Icons
Icons come from lucide-react. Don't import a second icon library.
- Default size:
size-4(16px).Buttonauto-applies this to any<svg>it contains via[&_svg:not([class*='size-'])]:size-4, so most call sites don't need to set a size on the icon. size-3/size-3.5: for metadata, captions, and dense list rows where 16px is too loud.size-7+: for featured/empty-state hero icons only.- Stroke width: lucide's default 2px. Don't override per-icon.
- Color: inherit from surrounding text —
text-muted-foregroundfor secondary,text-destructivefor destructive, etc. Don't apply a token to the SVG directly when the parent already carries the right color. - Spinner: the canonical loading icon is
<Loader2 className="size-4 animate-spin" />. For 3s+ multi-step work, prefer a label that names the stage ("Cloning…" → "Installing…") over an unlabeled spinner. See UX rule 1.
Keyboard shortcut chips
Use <ShortcutKeyCombo /> from src/renderer/src/components/ShortcutKeyCombo.tsx. It renders a consistent key-cap style and inserts a + separator on Windows/Linux (Mac shows adjacent glyphs, no separator). It does not transform key strings — the caller picks the platform-appropriate labels and passes them in:
const isMac = navigator.userAgent.includes('Mac')
const mod = isMac ? '⌘' : 'Ctrl'
const shift = isMac ? '⇧' : 'Shift'
<ShortcutKeyCombo keys={[mod, shift, 'N']} />See Landing.tsx for the canonical pattern. Don't roll a one-off <kbd> — kbd chips drift in shape and color across the app fast if everyone styles their own.
Where shortcuts surface in the UI:
- Tooltips on icon buttons — append the chip after the label, trailing.
- Dropdown / context-menu items — use
<DropdownMenuShortcut>(or its context-menu equivalent) for the right-aligned chip; don't position one yourself. - Never on Cancel, Dismiss, or
link-variant inline actions — see UX rule 3.
The label MUST match the actual binding for the platform. If the keyboard handler reads metaKey on Mac and ctrlKey elsewhere, the chip must show ⌘ on Mac and Ctrl elsewhere. Mismatched chips are worse than no chip.
Form anatomy
The pattern in src/renderer/src/components/settings/SettingsFormControls.tsx is the house style for any label + control + helper text. Match it for new forms:
- Outer stack:
space-y-3for full-section forms (ThemePicker);space-y-2for compact single-control fields (ColorField,NumberField). Pick by density, not preference. - Label group:
space-y-1containing<Label>and a description intext-xs text-muted-foreground. - Control: the shadcn primitive (``,
<Select>, etc.). Errors surface viaaria-invalid; the input primitive already maps that to a destructive ring — don't paint your own. - Trailing metadata:
text-[11px] text-muted-foregroundbelow the control (e.g., "Current: 14px · Default: 13px"), not next to the label.
Scrollbars
Three scrollbar classes are defined globally in main.css:
.scrollbar-sleek— the default thin, neutral scrollbar for sidebars, lists, popovers. Pair with.scrollbar-sleek-parenton a hover-target ancestor if you want the thumb to fade in only on parent hover..scrollbar-editor— slightly heavier, used inside Monaco-adjacent surfaces..worktree-sidebar-scrollbar— no reserved gutter: paired withoverflow-y-auto, the scrollbar (and its width) exists only while content actually overflows, so a short list stays flush with the fixed header controls and classic-scrollbar Windows shows no arrow buttons on empty lists. The thumb stays invisible until the parent (.scrollbar-sleek-parent) is hovered. Used only in the worktree sidebar.
Apply one of these to overflow containers; don't write a fourth style.
UX rules
These are the rules a contributor will most often get wrong if they're working in isolation. They apply to every UI change.
UI copy must not overclaim. Never imply the app has taken an action, made a decision, or observed a fact unless the code has real state or result data to support it. Use neutral process language while work is pending, and reserve result verbs like "skipped", "protected", "found", "verified", or "deleted" for actual results.
Screen UX review rubric
Use this rubric when reviewing any Orca IDE screen, screenshot, or prototype. A good review should name the highest-impact friction first, then give concrete changes the implementer can make.
Review output format
- Top fixes: the 3 changes that would most improve the screen.
- Friction notes: specific clutter, alignment, copy, focus, or flow issues, with the affected UI element named.
- Suggested changes: exact changes to layout, hierarchy, controls, copy, empty/error states, and disclosure.
- Keyboard and speed check: whether the primary workflow can be completed in 1-2 actions where appropriate, with good default focus and Enter/Esc behavior.
- Follow-up links or states: missing external links, acquisition actions, or persistent errors the user needs to recover.
What to judge
- Progressive disclosure: keep high-frequency actions visible and prominent. Move low-frequency actions out of the common pointer path into menus, overflow controls, detail drawers, or advanced sections. Do not make menus so long that the user has to scan unrelated actions; group or split them when they grow.
- Action hierarchy: the primary action must be obvious through placement, size, and
defaultbutton styling. Put high-frequency actions at the top of menus and in the most reachable toolbar positions. Secondary and rare actions should not compete with the primary action. - Click count: remove unnecessary intermediate steps. Common workflows should complete in 1-2 actions when the app already has enough information to proceed.
- Default focus: dialogs, popovers, and command surfaces should focus the field or primary action the user is most likely to use. If Enter submits, focus must land where Enter triggers the intended primary action. Esc should back out without adding visual noise to Cancel/Dismiss.
- Keyboard navigation: prefer searchable command surfaces for long option lists. Add search fields when users need to find repositories, branches, worktrees, agents, commands, settings, files, or providers from a list.
- Shortcut labels: show shortcut chips only for shortcuts that are actually implemented and useful at that location. Labels must match the platform binding. If a shortcut strategy is undecided, do not expose a placeholder label in product UI.
- Alignment: rows and columns must line up to a visible grid. Left-align text and labels for scanability; right-align numbers, counts, shortcuts, and trailing metadata when comparison matters; center-align only compact icon controls, empty states, and table cells where symmetry is the clearest read.
- Copy quality: displayed text must be typo-free, concise, and specific. Prefer direct verbs and concrete nouns. Remove filler like "please", "simply", "just", "you can", and generic success language that is not backed by state.
- Dialogs and overlays: choose a dialog size that matches the amount of input. Short confirmations stay compact; forms with multi-line text, path pickers, provider setup, or review content need a larger dialog or sheet. Floating surfaces must use the documented shadow/elevation and background treatment so they read as above the page.
- Empty and error states: when data is missing, show a direct action to acquire or configure that data. Use toasts for transient failures or confirmations; persist errors inline when the user needs to read, retry, copy, or act on the message.
- External links: add direct links when the user may need provider docs, token settings, billing/setup pages, Git provider resources, or troubleshooting context. Put links near the relevant empty state, error, helper text, or setup step instead of burying them in a generic menu.
- Affordance: users should be able to discover available features without intrusive education. Use familiar icons, visible hover/focus states, clear labels where needed, and tooltips for icon-only controls. Prefer simple lucide icons already used nearby over obscure alternatives.
- Layout density: avoid jamming controls together. Preserve breathing room around the primary workflow, reduce competing buttons, and keep toolbar groups visually distinct. Dense screens are acceptable only when grouping, alignment, and hierarchy make scanning faster.
- Cards and containers: cards must be visually distinct from their parent surface through the existing
card/bordertreatment. Avoid nesting cards inside cards. If a section is not a repeated item, modal, or framed tool, consider an unframed layout or full-width band instead. - Side-by-side layouts: default to row-by-row layouts for complex workflows because they are easier to align and scan. Use side-by-side layouts only when space is constrained or comparison is the point, then polish column widths, baselines, and wrapping states carefully.
- Animation: use subtle animation to soften expanding/collapsing content and prevent jumpy layout changes. Animation should clarify continuity, not decorate. Respect reduced motion element by element: turn movement off with
motion-reduce:(slides, rotations, size changes); give spinners, pulses and progress indicators a still state that still says "working" (LoadingSpinnerdoes this for spinners) instead of freezing them; short opacity fades can stay. Scripted scrolls ignore CSS, so pass them throughmotionSafeScrollBehavior(). - SSH and latency: assume actions may run remotely. Disable submit controls immediately, delay visible loading feedback when appropriate, and keep focus stable while remote data arrives.
1. Match in-flight feedback to perceived duration
The right question isn't "should this control change while it's working?" — it's "how long does the action take, and what does the user need to know during that time?"
| Duration | Feedback |
|---|---|
| 0–100 ms | None. Anything visible reads as a glitch. |
| 100 ms–1 s | Disabled state only. |
| 1 s–3 s | Disabled + spinner or label swap. |
| 3 s+ or multi-step | Stage labels, progress, optional reassurance. |
Two corollaries:
- Pre-reserve any space you'll later occupy. If a control may swap to a longer label or grow an icon, fix its footprint up front (use
width, notmin-width). A control that resizes mid-action looks broken even when the action succeeded. - Don't pick worst-case feedback for everyone. If the action is fast locally and slow remotely (SSH), defer the visible loading state by ~200ms. Local users see nothing; remote users get appropriate feedback. Bind the disabled state immediately (so double-clicks don't double-submit) and the visible state on a timer.
2. Look for sibling components before designing in isolation
If your component has a sibling — same domain, overlapping behavior, often visible at adjacent moments in the same flow — the two should read as one design. Same icons, same shortcut conventions, same submit semantics. A user moving between them shouldn't perceive a seam.
This is not "match every existing pattern." Some repo patterns are debt and copying them spreads the debt. The narrower claim is about adjacent components. Diverging from a sibling needs a reason: either the sibling is wrong (fix both) or the new component has a real difference in role (commit to it).
When there's no sibling, match the surrounding chrome — button sizes, icon weights, copy tone — and don't manufacture a sibling from a screen the user will never correlate with this one.
3. Don't overload the back-out path
destructive is for actions that lose data or can't be undone. Cancel, Dismiss, Close, and Discard are not destructive — they back the user out of an in-progress action and should stay quiet (default ghost button, no color, no keyboard chip, no animated affordance). Save the visual weight for the affirmative action so the two don't compete. Keyboard handlers can still honor Esc; the visible decoration is what stays minimal.
Cross-platform
Orca runs on macOS, Linux, and Windows. Every UI change must hold up on all three, in both light and dark mode.
- Modifier keys: Never hardcode
e.metaKey. Usenavigator.userAgent.includes('Mac')to choosemetaKeyon Mac andctrlKeyon Linux/Windows. Electron menu accelerators should useCmdOrCtrl. - Shortcut labels: Display
⌘/⇧on Mac; displayCtrl+/Shift+on other platforms. The label must reflect the actual binding for that platform. - Window chrome: macOS shows traffic lights; the titlebar reserves an 80px gutter (
titlebar-traffic-light-pad) so they don't overlap content. Don't put hit targets in that band on Mac. - SSH: Many users run Orca on a remote machine. Loading states, focus management, and animations must hold up under 50–200 ms of extra latency. Test under simulated latency (or actual SSH) — local-only verification isn't enough. See UX rules → 1.
When this guide is silent
If you have a UI question this doc doesn't answer:
- Look at adjacent code in
src/renderer/src/components/for the closest sibling, and follow its lead. - Check
src/renderer/src/components/ui/for a primitive that already encodes the pattern. - If it's a token question,
main.cssis canonical — use what's there, or add a new one in both light and dark. - If none of those resolve it, ask the user before inventing.
Agent Skill Sharing Implementation Checklist (`docs/agent-skill-sharing-implementation-checklist.md`)
Agent skill sharing implementation checklist
Status: implementation and validation in progress.
Last updated: 2026-08-12.
Implementation baselines captured by this checklist update:
- Orca implementation:
skills-sharethrough607e31dd04; no PR. - Orca Cloud: bundle smoke PR
#329merged aseddb144afe; generation-aware recovery PR#330
merged as8045c85dad; encrypted physical-host credential PR#336merged as8fce3298ef;
kill-switch discovery PR#342merged asc2bef2ff20fb.
Windows device-name validation PR#343merged asdbb14a658cbc; explicit disabled production
bootstrap PR#352merged as2a23f6ace5; publisher-identity omission PR#355merged asb3213bd34d1b224d8a3b11527eceaac883965400; production enablement PR#357, bounded OIDC smoke
PR#358, monitor IAM PR#359, and monitor release-ownership PRs#360/#361are merged
throughd8605edde72229c5d00d22c8bd6853a13c1a4aaa.
Validated so far:
- Current release hardening cancels bundle extraction after partial bytes exist and removes the
extraction directory, durable journal, and managed-install state. Four repeated focused runs and
the real SIGKILL recovery test passed. Signed GCS uploads use the earlier of signed-policy expiry
and a 15-minute cap, including their archive read stream, so slow valid uploads can finish while
stalled uploads remain bounded. - Local and headless managed-skill operations now wait for the one startup transaction-recovery
promise before reading or changing install state. Codex uses its documented global~/.agents/skillsroot, and provider removal derives allowed roots from the registry so aliases
remain removable after an agent is no longer detected. The combined focused suite passed 74
tests across 12 files, including fake-timer slow-upload and controlled-recovery regressions. - The reported desktop install failure was a strict main-process IPC schema rejecting the
renderer's additiveprovidersselection before bundle verification. All four install schemas
now accept the bounded field, and focused schema/provider tests pass. The original production
link resolves as a valid one-skill bundle and returns a fresh same-version local download grant;
the signed URL and private metadata were not logged or printed during verification. - The final reliability hardening at
607e31dd04adds a 60-second Cloud request deadline;
cancellable, journaled bundle extraction; bounded package creation, verification, and preview
fanout; atomic share-preparation admission and abandoned-archive cleanup; process-owned download
staging; SSH startup recovery; and additive paired-client cancellation-result negotiation. The
focused suite passed 78 ordinary tests plus a real process-death recovery test. Full Node, CLI,
and web typechecks, repository lint and reliability gates, changed-file formatting, and diff
checks passed. Mobile-facing wire changes remain additive, and no mobile app surface changed. - A final performance and React lifecycle review found one stale-response race in managed-install
inventory: a slower previous machine could overwrite the newly selected machine's list. The
generation fence and regression test now preserve destination ownership; bundle busy state also
reaches the parent close guard in the initiating event instead of a mirror effect. The complete
renderer skill/settings slice passed 122 tests across 18 files, all Node/CLI/web typechecks,
focused Oxlint, changed-code quality, max-lines, diff, and localization gates. - The final local readiness pass at
9fcd72e327passed 489 skill-domain tests across 73 files
with 11 intentional platform skips, all Node/CLI/web typechecks, localization catalog,
extraction and coverage, max-lines, changed-code quality, React Doctor, formatting, and diff
checks. No new P0, P1, or P2 finding was proven by the crash/retry, security, performance,
functional, mobile-backcompat, or Windows/Linux review. - Multi-select sharing now preserves hidden selections, reconciles changed discovery scans,
visibly explains ineligible skills, and blocks duplicate names before review. Exact complete
managed bundles publish a new immutable version to their existing package; ambiguous or partial
selections safely create a new package. Managed bundles with an active link can reopen the
destination-aware installer for another machine; the final renderer slice passed 114 tests. - The mobile/backcompat review found no persisted mobile keys, routes, deep links, or required
legacy fields changed. Skill RPC methods and capabilities are additive and capability-gated;
the cross-platform review found production paths use host path APIs and platform checks, with
POSIX-only literals confined to tests or WSL guest execution. - Final bearer-link changes pass 143 Orca Cloud API tests with one opt-in skip; 58 focused Orca
tests; four mixed-version wire tests; desktop/node/web typecheck; localization catalog,
extraction, and coverage; changed-code quality; max-lines; and diff checks. - The final Cloud change passed PR run
31564069382and merged-mainrun31564235724, including
the full monorepo build, lint, typecheck, tests, Terraform format, and Terraform validation. - The opt-in desktop staging harness now publishes a bearer share without retired audience fields;
Node typecheck and Playwright test discovery pass without starting Orca or a browser login flow. - Local Node and web typechecks, changed-code quality gates, 94 skill-domain files with 770 tests
passed and 3 skipped, 134 Orca Cloud API tests with one opt-in integration skip, the full Cloud
monorepo test/typecheck/lint/build gates, and isolated Terraform formatting and validation. - Final Orca validation passed repository-wide lint, all Node/CLI/web typechecks, the production
desktop/native build, the Node 18 Relay bundle contract, 78 focused skill files with 557 tests
passed and 10 platform skips, and the mixed-version wire suite. The full repository run passed
49,977 tests with 112 skips; its three environment/timeout failures passed as a 124-test rerun
after removing Orca-injected Git config variables and rerunning the slow macOS PTY case. - Skill sharing and installation renderer copy now passes the localization catalog, extraction,
and coverage gates without a max-lines exception. - Remote upload sessions actively expire abandoned bytes after their bounded idle lifetime, and
begin/chunk/commit retries resume from the host-acknowledged offset under one stable transfer ID. - Paired skill install and upload cancellation now reaches queued calls, capability probes,
one-shot sockets, cached request sockets, and shared-control requests instead of waiting for the
five-minute transfer timeout. Focused coverage proves queued work never starts, pending request
admission is released, late responses are ignored, one-shot sockets close, and unrelated cached
or shared-control requests survive; 84 transport/service tests and Node typecheck passed. - Standalone and bundled package names reject Windows reserved device names in both Orca and Cloud,
preventing a package published on macOS/Linux from failing only when installed on Windows. - Identical archives are deduplicated only within an owner tenant. PostgreSQL object identity and
tenant-hashed GCS keys prevent cross-tenant existence or finalization-timing disclosure. - Native Windows package/install/recovery/copy-fallback tests and Node typecheck on
windows 2;
the current slice passed 284 tests with 21 intentional platform skips across 43 files. - The final combined physical
windows 2run at44d1266641enabled native-Windows, real-process,
and Ubuntu 24.04 WSL coverage together: 67 files passed, 449 tests passed, and 17 intentional
platform skips remained. Commit97b831dd17made Windows recovery assertions path-semantic and
time-deterministic. Commit44d1266641fixed WSL alias reconciliation after proving native UNClstatcan miss an existing distro symlink; WSL now inspects the alias in-distro and creates new
aliases withln -sT. The checkout remained clean and noorca-skill-*directory remained in
WSL/tmp. - Native Windows transaction validation passed all 21 cases at
6d3ce582aa, including a real
canonical install beyondMAX_PATHwhile the host'sLongPathsEnabledpolicy remained disabled. - Real Ubuntu 24.04 WSL global, guest-workspace, and
/mnt/cworkspace transactions, including
alias placement, interrupted update recovery, conflict preservation, update, and removal. - Real Ubuntu 24.04 WSL provider detection and POSIX semantics, including case-sensitive names,
0600regular files, and0700executable files. - Exact portable package digest and archive SHA-256 goldens passed on macOS and native Windows
despite different bundled zlib versions (1.2.12and1.3.1). - The focused deterministic package suite passed 26/26 tests in Debian Bullseye/glibc 2.31 on
native Linux ARM64 and emulated Linux x64, plus the real Ubuntu 24.04 WSL distro onwindows 2;
every host produced the same package digest and archive SHA-256 golden. - The same 26/26 portable package suite passed at
109272dab1on a disposable native x86_64
Ubuntu 22.04 GCE host using Linux 6.8 and Node 24.18. The VM had no service account or scopes;
its boot disk, uploaded Git bundle, and temporary project SSH metadata were removed afterward. - The exact x64 Electron directory package at
f1dccb4f42passed its after-pack ABI scan for all
18 bundled native binaries, then loaded packagednode-ptyand spawned/bin/shinside an
Ubuntu 20.04/glibc 2.31 container. The physical run corrected the gate to use Linux's actualorca-ideexecutable andresources/node_moduleslayout. A native ARM64 package built from
exact commitabe92d565bon an isolatedaarch64GCE host then passed the same 18-binary
Ubuntu 20.04/glibc 2.31 ABI gate and packagednode-ptyload/spawn smoke. The ARM64 VM had no
service account or scopes; its auto-delete boot disk was verified absent, and its local Git
bundle was moved to Trash after the clean-checkout proof. - Real native Windows global, linked Git-worktree, and plain folder installs with spaces and
non-ASCII paths, plus privacy-safe install diagnostics and owner-private staging tests. - Docker-backed SSH relay installation from the real Electron client passed global, remote Git
worktree, and remote plain-folder scopes, including client-mediated upload, unchanged preview,
managed-install listing, safe removal, and remote-target Cloud authorization. - Real paired-runtime installation passed from an isolated Electron client to both a headed
desktop host and headlessorca servehost. The host owned global and Git-worktree installs;
the headed host also owned a plain-folder install. The client's home remained untouched. - A fresh post-resilience E2E run passed all three headed paired-runtime, headless
orca serve,
and Docker SSH journeys. Focused failure injection also proves invalid gzip classification,
old-version preservation afterEACCESandENOSPC, bounded recovery from a lost final install
response, and validated structured SSH failure data without arbitrary error-data disclosure. - Isolated staging bucket, IAM,
orca_skillsdatabase and principal, enabled secret version, 11
log metrics, four alerts, and one dashboard inonorca-cloud-staging. The complete targeted
skill-infrastructure plan is zero-diff at Cloud41ef335. - PostgreSQL migration startup is serialized by a transaction-scoped advisory lock. Transactional
DDL rollback and eight concurrent startup callers passed against ephemeral PostgreSQL 16 and 17,
with exactly one recorded schema version. - Cloud PR
#349merged as6a812c5e11after proving an older API migration runner preserves a
newer additive column/table and continues using its existing SQL. Build, lint, typecheck, tests,
Terraform initialization, and validation passed; no service or infrastructure was deployed. - Cloud PR
#350merged asc22007384aand added a required disposable PostgreSQL 16/17 drill.
Real old and future-additive Node processes coexist and write against one database, the database
is dumped and restored, and both processes write again after restoration. PR run31624484826
and merged-main run31624717796passed every code, Terraform, and database job; no staging or
production resource was accessed or deployed. - The immutable API deploy path stamps every artifact and skill literal variable plus exactly one
approved skill-database secret, and rejects secret, Cloud SQL, service-account, scaling, CPU,
memory, volume, mount, probe, or unexpected-environment drift before traffic promotion.
Staging intentionally carries all four skill controls astrue; production remainsfalse. - The route-disabled baseline, API revision
orca-cloud-api-staging-00039-rek, first proved the
database-ready startup and anonymous401/404boundaries with zero revision errors. - Cloud PR
#313passed run31533988351, merged asd0bf926, and passedmainrun31534229437. Guarded wake run31534564230restored exactly the two configured Relay cells. - Auth deploy run
31535179937promoted revisionorca-cloud-auth-staging-00017-dugat 100%
traffic. Health, JWKS, exact GitHub OIDC claim constraints, and zero deployment-window errors
passed. - The historical ACL-era API deploy run
31535438327promoted revisionorca-cloud-api-staging-00042-hefat 100%
traffic withauthenticatedSmoke: trueandskillSmoke: true. The smoke covered artifact
lifecycle; skill upload/finalize/download; two immutable versions; recipient and outsider
authorization; local and remote grants; rollback selection; expiry; revocation; package
deletion; and signed-object cleanup. - Cloud PR
#317passed both required checks and merged asbe00db10. Its historical ACL-era
deploy run31546194596
promoted revisionorca-cloud-api-staging-00048-xomat 100% traffic withauthenticatedSmoke: trueandskillSmoke: true; canonical health and the immutable image
digest independently matched after promotion. - The signed-in macOS desktop staging journey reached upload, finalize, durable share creation,
and global install after replacing an invalid colon in the audience-derived idempotency key.
The retry test now requires a stable path-safe key, and the live test records the package ID
before publication so a failed share can still clean up its finalized package. Its update phase
uncovered same-name discovery ambiguity; the harness now selects the exact workspace source
directory instead of the first same-named global skill. A final full live rerun remains open. - The serving API has all four controls
true, zero revision errors, and privacy-safe structured
request logs. The log field inventory contains no principals, credentials, signed URLs, object
paths, or private contents; a sensitive-value scan returned zero matches. - Cloud PR
#314removed deploy-owned Auth release metadata from Terraform ownership, passed run31536141357, merged as4e91cf9, and passedmainrun31536329459. The targeted Auth/API
plan then reported zero intended changes. Known SQL and artifact-bucket drift was excluded and
never applied. - Cloud PR
#320passed both required checks and merged as0579cc1a71. Targeted Terraform added
onlygoogle_logging_project_exclusion.skill_share_bearer_request_urlsin staging: one addition,
zero changes, and zero deletions. This prevents per-link Cloud Run request URLs from entering
log storage while retaining route-template application telemetry. - Guarded wake run
31564370807prepared the reviewed staging topology. Deploy run31564803943
promotedorca-cloud-api-staging-00051-yoqat 100% traffic with immutable image digestsha256:511c0196511d2079bd9138092ad3cf065304b46a089b02d8df9318e0ae2e656aand reportedauthenticatedSmoke: trueandskillSmoke: true. - Candidate and canonical smoke produced 60 privacy-safe route-template events covering owner
upload/finalize/share creation/inventory/revocation and anonymous pinned/latest preview,
local/remote download grants, version selection, expiry, uniform unavailable responses,
package deletion, and object cleanup. Anonymous requests with no Authorization header and an
invalid header both returned the same404 skill_share_not_foundresponse withno-store.
The serving revision had zero error logs and Cloud Logging retained zero per-link platform
request logs. - Guarded sleep run
31546908228stopped all three Relay cells after the physical staging work.
Independent GCP reads verified Cloud SQL policyNEVER, every MIG target at zero, and both
managed Cloud Run minimums at zero. - Final guarded sleep run
31565009141restored the same low-cost state after the bearer rollout;
independent reads found Cloud SQLNEVER/STOPPEDand C1, C2, and C3 at target size zero. - Cloud PR
#329passed required checks, merged aseddb144afe, and passed merged-main run31569732482. Browser-free desktop run31569902499then passed bundle publish v1, install,
local modification preservation, v2 update, rollback, revoke, installed-copy preservation, local
removal, and Cloud package deletion without opening a login page. - Cloud PR
#330passed required run31578720429, merged as8045c85dad, and passed merged-main
run31578942857. Guarded wake31579195133brought SQL and all three Terraform-owned cells up;
deploy31579844413promotedorca-cloud-api-staging-00057-katat 100% traffic with immutable
digestsha256:f61745b21d00b111087620ab1108c9a96e9863ec9e902a424aaad01e9e945605. - Recovery smoke
31580071168published an isolated bundle, soft-deleted the exact published GCS
generation, restored it withifGenerationMatch=0, verified immutable metadata, transactionally
repointed every matching database reference, verified the unlisted bearer download, and deleted
the probe. No ephemeral recovery job or live probe object remained. - Guarded sleep
31580339694returned staging to low cost. Independent reads verified Cloud SQLNEVER/STOPPED, C1/C2/C3 target size zero and stable, and API/Auth min scale zero. Production
was not touched. - Cloud PRs
#332through#335added the staging-only 12-by-30 finalization load gate, narrowly
authorized its OIDC identity, fixed deterministic fixture ordering, and made retry settlement
cleanup-safe. Their merge commits wereac85d0690e,53559bd644,7e1d50a84b, andbb8bf8b9ac; merged-main verification passed through run31586525326. - Auth deploy
31584318896promotedorca-cloud-auth-staging-00021-tuqwith digestsha256:e17075d69dca36df427f02fa515481f359c8f67397c5ab16de9759f7f949c6be; candidate and
canonical smoke passed with one 100%-traffic revision and no remaining candidate tag. - Load run
31585710645exercised 12 concurrent bundles with 30 skills and 3,949,317 extracted
bytes each. Two finalized immediately and ten returned explicit saturation; client p95 was
1,514 ms. Aggregate utilization stayed bounded: request p95 1,401.06 ms, API CPU 8.95%, API
memory 26.95%, one API instance, database CPU 10.89%, database memory 25.59%, and seven
connections. A saved, exact one-add Terraform plan restored the declaredroles/monitoring.viewerbinding for the staging deploy identity before those aggregate metrics
were read; it changed no service, traffic, SQL, storage, Relay, or production resource. - Cleanup run
31586684354removed the exact failed-wave package set through normal package DELETE
routes. The two remaining quarantine archives were manifest-proven as 30-skill fixtures for
suffix013756ec4740and deleted with exact GCS generation preconditions; no live object from
that run window remained. GCS soft delete keeps the operation recoverable. - Guarded sleep
31587083752returned staging to low cost. Independent reads verified Cloud SQLNEVER/STOPPED, C1/C2/C3 target size zero and stable with no current actions, and API, Auth,
and Relay active-revision minimums at zero. Production was not touched. - Cloud PR
#336added a browser-free physical-host credential handoff restricted to the exactmain-branch staging workflow identity. It encrypts only the ten-minute owner token to a
one-time RSA-3072 key, grants no GCP permissions, retains ciphertext for at most one day, and
passed PR run31588982191plus merged-main run31589194501as merge8fce3298ef. - Guarded wake
31589384191restored SQL and the two configured Relay cells without waking C3.
Auth deploy31589963244promotedorca-cloud-auth-staging-00025-zuzat 100% traffic with
immutable digestsha256:2b5cb04060a871f372298786dbed681054a076f5cf75966ea5e2130403db7254. - The physical
windows 2staging journey passed in 27.6 seconds using credential run31591275227. It installed v1 into the Windows-owned global path with the published digest and
no macOS fallback, updated only that host to v2, preserved independent local version selection,
rolled Windows back to v1, proved revocation left its installed copy intact, removed both host
and local installs, and deleted the Cloud package. The encrypted artifact and local one-time key
material were deleted immediately after use. - The staging skill bucket independently reports the exact age-one-day Delete lifecycle restricted
touploads/, seven-day soft deletion, uniform bucket access, and public-access prevention. Its
oldest live quarantine object was created at2026-08-11T22:49:34Z, so lifecycle execution—not
configuration—is still time-gated. - Guarded sleep
31592709817completed at Cloud8fce3298ef. Independent reads verified SQLNEVER/STOPPED, all three Relay MIGs stable at target zero with no active actions, and API,
Auth, and Relay active-service minimums at zero. - Real process-death recovery now kills the transaction process after partial extraction and
immediately before and after every durable install/removal journal transition. All 17 macOS
cases passed, including extraction cleanup, dead-lock reclamation, receipt/filesystem agreement,
and absence of transaction debris; the release workflow requires the same suite on macOS,
native Windows, and Ubuntu 20.04/glibc 2.31. - Guarded wake
31603249983restored only the two configured staging cells. A disposable
no-service-account Ubuntu 20.04/glibc 2.31 VM then passed the browser-free SSH staging lifecycle
in 31.3 seconds: publish v1, host-owned install with exact digest, v2 update, managed-install
verification, rollback, revocation with installed-copy preservation, local removal, and Cloud
package deletion. The remote install, Orca SSH target, encrypted credential artifact, one-time
keys, and VM were removed. Guarded sleep31604391897passed; independent reads verified SQLNEVER/STOPPEDand C1/C2/C3 target zero, stable, and reached. Production was untouched. - Guarded wake
31634214856restored the two configured cells at Clouddfb8359ff5. An isolated
nativeDarwin arm64OpenSSH target with a separate temporary home then passed the full staging
lifecycle in 20.1 seconds: publish v1, SSH-owned install, v2 update, managed-version checks,
rollback, revocation with installed-copy preservation, removal, and Cloud package deletion.
This corrected the staging oracle to accept absolute POSIX homes outside/home. The GitHub
ciphertext artifact was deleted immediately; the SSH service, relay, install, temporary home,
and one-time keys were removed. Guarded sleep31635145830passed; independent reads verified
SQLNEVER/STOPPED, all three MIGs stable and reached at zero with no active actions, and
API/Auth/Relay minimum scale zero. Production was untouched. - Guarded wake
31605729090restored the same configured topology. An isolated headless Orca host
with a separate home/profile then passed the full paired-runtime staging lifecycle in 12.5
seconds, proving host-owned install, update, managed-install state, rollback, revocation
preservation, removal, and Cloud deletion without local fallback. The paired environment,
install, package, encrypted credential artifact, and one-time keys were removed. Guarded sleep31606600532passed; independent reads again verified SQLNEVER/STOPPEDand all MIGs at
stable, reached target zero. - Guarded wake retry
31630802215succeeded at Clouddfb8359ff5; the additional physical WSL
Cloud journey was then waived as duplicate release evidence. The first guarded sleep attempt31631288043failed closed during Terraform provider initialization on a transient upstream503, before the mutation step. Retry31631379891succeeded. Independent reads verified SQLNEVER/STOPPED, all three MIGs stable and reached at target zero with no active actions, and
the API, Auth, and Relay active-revision minimums at zero. Production was untouched. - Cloud PR
#355removed internal publisher and organization identifiers from anonymous skill
responses, passed every code, Terraform, and PostgreSQL 16/17 check, and merged asb3213bd34d1b224d8a3b11527eceaac883965400. Guarded wake31639920301prepared staging. Deploy31640677572promotedorca-cloud-api-staging-00060-qayat 100% traffic with immutable digestsha256:31cb0a91a7abf1f82e4de08bd31e98fbd71519adc731a005fc95f60480658f73; authenticated and skill
candidate/canonical smoke passed. The unrelated post-promotion storage-monitor image update
lackediam.serviceAccounts.actAs; the unchanged prior monitor image remains serving, and the
API promotion and verification completed successfully. Guarded sleep31640935616passed in
8m51s. Independent reads verified SQLNEVER/STOPPED, C1/C2/C3 stable and reached at target
zero, and the API at minimum scale zero with revision00060-qayretaining 100% traffic.
Production was untouched.
Rollout gate: staging infrastructure, OIDC owner identity exchange, anonymous bearer lifecycle,
owner management, browser-free desktop bundle lifecycle, privacy-safe logging, published-object
recovery, bounded finalization load, and guarded rollback-capable deployment passed. Production
infrastructure and the unlisted-link API are live with all four controls enabled after successful
authenticated candidate and canonical lifecycle smoke. Native Windows, real Ubuntu 24.04 WSL
filesystems, physical Ubuntu 20.04 and
macOS ARM64 SSH, and paired non-Windows staging passed. On 2026-08-12, the owner accepted the
remaining live WSL-to-staging journey as duplicate evidence after the combined 449-test physical
Windows/WSL run and native-Windows staging lifecycle; this does not treat WSL semantics as
identical to macOS. The user-driven signed-in desktop and real-host production journey, supported
Windows SSH, and the quarantine lifecycle deletion remain. The shared staging data plane is asleep.
This checklist turns the architecture plan into ordered implementation and release work. A checked
item means evidence exists in code, tests, reviewed infrastructure, or release documentation; it
does not mean the surrounding phase is complete.
Definition of done
- A user can select one or many private local skills, publish one immutable Skill Bundle, and
receive one durable Orca share URL. - Anyone with an active unlisted link can inspect a bundle without signing in, choose all or a
subset of its skills, and install them globally or into a Git worktree or plain folder
workspace. - Installation works on local macOS, Linux, native Windows, WSL, paired Orca runtimes, and
supported SSH targets. - The portable archive root conforms to Agent Plugins 1.0.0 for skills-only packages and keeps
Orca integrity metadata indev.orca.skill-sharing/manifest.json. - One canonical
.agents/skills/<skill-name>copy is installed for each selected skill;
provider-specific placements are reconciled for explicitly selected agents, defaulting to
detected agents when no selection is supplied. - Install, update, rollback, and removal preserve modified or unowned local content unless the
user explicitly approves replacement. - Package ingestion rejects unsafe archives before any destination mutation.
- Interrupted operations recover to a complete previous or requested version.
- Private package bytes remain access-controlled, published versions persist until deletion,
and incomplete uploads expire automatically. - Mixed-version clients and remote hosts fail safely through capability negotiation.
- Cloud resources are Terraform-owned, monitored, recoverable, and protected by independent
upload, download, and remote-install kill switches. Targeted Terraform is zero-diff; staged
generation recovery passed; dashboards, alerts, and runbooks cover the data plane; and Cloud
PR#342proves all three mutation controls can be disabled independently while unlisted
preview remains available. - The existing Skills page supports multi-select sharing, selective installation, per-skill
conflicts/results, and installed-bundle management; Settings → Share Skills provides the
authenticated owner inventory for copying and revoking active links.
0. Confirm scope and ownership
Product and legal decisions
- Record
vercel-labs/skillsas a behavioral reference only. - Record upstream baseline commit
c6f69c631292444cc541ac6d91e2226b0ff247da. - Decide not to copy upstream source, tests, fixtures, registry data, or path tables.
- Decide Orca will not depend on the upstream CLI or an unsupported programmatic API.
- Decide V1 accepts Orca package sources only; Git, npm, and community registries remain with
existing tools. - Decide every Skill Bundle share is unlisted and reachable only through its durable link; do
not add search, browsing, organization-library, marketplace, or contained-skill indexes. - Record Agent Plugins 1.0.0 commit
bd383552095128f6effe895b9257cfd580a6d179
as the portable-format reference. - Decide to implement from the specification without copying its CC BY prose or vendoring its
Apache-licensed schemas. - Treat the archive as a Skill Bundle, not an Orca plugin product or executable plugin runtime.
- Limit V1 bundles to skills; exclude MCP servers, hooks, processes, connectors, and
permissions. - Review the architecture decision record covering the reference-only boundary with
engineering and legal owners. - Add a review check that flags any proposed copied or mechanically translated upstream
material so attribution and licensing can be reconsidered before merge. - Assign desktop, runtime, Cloud API, Terraform, security, design, and release owners.
- Confirm the first release ends after WSL and SSH support; keep team reconciliation as a later
product milestone.
Product semantics
- Confirm private means unlisted and protected by an unpredictable revocable bearer link, not
end-to-end encryption from Orca Cloud operators. - Confirm a published immutable version persists until package or version deletion; it has no
age-based object expiry. - Confirm an unfinished upload grant expires after 15 minutes and its quarantine object is
deleted after one day. - Confirm a durable share URL contains a bearer credential for a revocable authorization
record, not a GCS object or permanent blob grant. - Confirm a signed download grant lasts five minutes and is the maximum share-revocation lag.
- Confirm updates create immutable versions and never mutate existing package objects.
- Confirm rollback installs a selected prior immutable version.
- Confirm revoking or deleting a Cloud share does not silently remove existing local installs.
- Confirm sharing and updating use an artifact-like flow—preview, upload, unlisted durable
link, update, revoke/delete—while retaining skill-specific version, trust, and install
semantics. - Confirm recipients do not sign in; publishing and managing owned links still require sign-in.
- Confirm revocation blocks future resolution/download grants but does not remove installed
copies. - Add a Settings section named Share Skills, parallel to Artifacts, without adding any
index or discoverability surface. - Treat the authenticated owner-only active-link inventory as management, not recipient
discoverability; never expose it anonymously or through browse/search APIs. - Confirm V1 has no checked-in workspace desired-state lockfile and no automatic fleet-wide
installation. - Confirm one skill is a one-item bundle and a bundle may contain large selections such as 30
skills behind one durable link. - Keep bundle identity, access, versions, transport, update/rollback, revocation, and deletion
at bundle scope. - Keep installation selection, conflicts, provenance, modification protection, and outcomes at
skill scope. - Keep loose-skill installation as the universal/default path and treat provider-native plugin
adapters as optional compatibility outputs.
Existing-code and provider research
- Inventory current skill discovery,
observeSkillPackage, package identity, topology, and
freshness code; record reusable contracts and missing behavior. - Inventory plugin staging, provenance, lock serialization, and Windows rename retry designs;
extract only primitives that both domains can name and test accurately. - Inventory current desktop IPC and remote skill RPC registration.
- Inventory existing SSH execution and file-transfer providers and identify the host-side
installer entry point. - Research official documentation for every initially supported agent's global and project
skill directories. - Verify each provider path with a real installation on every supported platform.
- Record whether each provider reads
.agents/skillsdirectly, how it is detected, and which
alias mechanisms it supports. - Add a normal reviewed maintenance process for provider registry changes; do not create an
upstream synchronization job.
1. Define package and install contracts
Bundle contract replacement
- Replace the unpublished single-skill envelope with root
plugin.json,skills/<name>/, anddev.orca.skill-sharing/manifest.json; do not add an outerbundle/directory. - Define
SkillBundleManifestV1with stable package/version identity, bundle metadata, ordered
skill entries, per-skill identity and file lists, globally unique archive paths, and a bundle
digest. - Validate Agent Plugins 1.0.0
plugin.jsonfields from independently authored local rules;
never fetch a schema during loading. - Create fresh staging roots and reject unknown, malformed, or conflicting imported
dev.orca.skill-sharingnamespaces without overwriting them. The extractor now exclusively
creates its staging root, preserves a pre-existing namespace on conflict, rejects unknown
top-level extension entries, and removes only staging it created. - Keep the detailed manifest inside the archive for export, remote/SSH transport, and offline
validation; keep GCS metadata compact and PostgreSQL limited to exact link/ownership lookup. - Add selected skill IDs and per-skill conflict choices to preview/install requests.
- Return aggregate status plus ordered installed, unchanged, kept-local, and failed per-skill
outcomes; support retrying failed items only. - Add
skills.install.bundle.v1and additive bundle RPC methods so mixed-version peers fail
before transfer. - Decide to dispose of the unpublished staging-only single-skill records before changing the
Cloud schema; there
is no production compatibility commitment to the unpublished single-skill format.
Shared package manifest
- Add
src/shared/skill-package-manifest.ts. - Define the original single-skill
SkillPackageManifestV1prototype with schema version,
stable package ID, immutable version ID, name, description, creation time, file identities,
and package digest. - Normalize manifest paths to
/while keeping filesystem conversion host-owned. - Define the canonical digest algorithm over normalized paths, file identity, executable state,
and classification. - Require
skill/SKILL.md, valid frontmatter, and agreement between skill and package names. - Specify deterministic serialization so identical inputs produce identical identities across
macOS, Linux, Windows, and WSL. - Version the schema and require additive changes or a new schema version.
- Reject unknown schema versions with a stable error category.
Package limits and file policy
- Enforce maximum path depth 16.
- Enforce maximum 2,048 archive entries.
- Enforce maximum 512 regular files.
- Enforce maximum 4 MiB per file.
- Enforce maximum 32 MiB total extracted bytes.
- Enforce maximum 40 MiB compressed bytes.
- Reject absolute, parent-traversal, NUL-containing, and Windows drive-prefixed paths.
- Reject duplicate normalized paths and Unicode or case-fold collisions.
- Reject symlinks, hardlinks, devices, FIFOs, sockets, encrypted entries, and other special
files in V1. - Preserve executable modes represented by the package manifest.
- Document that install never executes package scripts.
Install request, preview, and result
- Add
src/shared/skill-install-contract.ts. - Define requests with operation ID, immutable package identity, ingress kind, destination, and
optional conflict resolution. - Support download-grant, staged-upload, and trusted in-process local-file ingress.
- Ensure arbitrary remote RPC callers cannot supply a local filesystem path.
- Model global, Git worktree, and folder-workspace destinations without assuming Git exists.
- Define preview output for destination, current state, provider coverage, conflicts, and trust
metadata. - Define structured installed, updated, unchanged, conflict, partial, and failed results.
- Define stable error categories for admission, transport, archive, filesystem, conflict,
recovery, provider placement, and compatibility failures. - Ensure responses never include grants, credentials, ACL membership, or private package
contents.
Capability and wire compatibility
- Add
src/shared/skill-install-capability.tswithskills.install.v1and an update-required
compatibility message. - Read and apply
docs/reference/remote-wire-compatibility.mdbefore changing RPC contracts. - Add new methods without changing existing discovery behavior.
- Keep new request and response fields optional until all supported peers understand them.
- Do not add a terminal stream opcode for skill installation.
- Define behavior for new client/old host, old client/new host, and capability loss during an
operation.
Phase 1 contract gate
- Review package, ingress, conflict, result, and capability contracts. Shared schema, runtime
method, Relay handler, ingress, planner, and stable-failure suites now form the release-gated
contract evidence; no existing discovery method or stream opcode changed. - Freeze stable V1 error categories used by desktop, runtime, SSH, and Cloud tests.
2. Build and validate packages
Package creation
- Add
src/main/skills/skill-package-creation.ts. - Accept a specific skill directory rather than an arbitrary parent tree.
- Observe and validate the source before staging.
- Copy the source into an owner-private staging directory.
- Observe the staged copy again and fail if its identity differs from the source snapshot.
- Generate a deterministic tar archive with
manifest.jsonand askill/envelope. - Include only
skill/contents in the eventual installed directory. - Bind the user-visible share preview to the final staged digest.
- Clean staging files on success, cancellation, source drift, and error.
- Accept a non-empty ordered selection of skill directories and support at least 30 within the
global entry/file/byte limits. Focused local creation/extraction now covers one 30-skill
archive; staging load run31585710645finalized the same maximum selection through Cloud. - Reject duplicate normalized skill names and preserve selection across search/filter changes
in the caller. The picker keeps hidden selections, unions explicit filtered selection, and
disables a second same-name source before review. - Generate deterministic
plugin.json,skills/, and Orca extension metadata. - Bind the share review to every staged skill digest and the final bundle digest.
Bounded extraction
- Add
src/main/skills/skill-package-extraction.ts. - Stream archive inspection and extraction without buffering the entire package.
- Validate
manifest.jsonbefore trusting archive file metadata. - Enforce compressed, extracted, entry, file, depth, and per-file limits during extraction,
not only after it. - Create extraction staging on the same filesystem as the canonical destination.
- Convert manifest paths with the destination runtime's path APIs.
- Re-observe extracted
skill/and compare every file identity and package digest. - Delete partial extraction bytes on cancellation or failure.
- Validate the portable root, Orca extension namespace, bundle manifest, and every selected
skills/<name>subtree before destination mutation. - Extract selected skill subtrees without requiring installation of unselected skills.
Package tests
- Test deterministic manifests, archives, and digests on macOS and native Windows. The exact
golden passed on both hosts at8be7275d0dafter replacing zlib-dependent streaming output
with bounded deterministic stored DEFLATE blocks. - Repeat the portable package golden on native Linux and inside WSL.
The focused golden passed in Debian Bullseye/glibc 2.31 containers on native ARM64 and
emulated x64, and inside the real Ubuntu 24.04 WSL distro, at92c8c6a4c6. The same 26/26
suite passed at109272dab1on a disposable native x86_64 Ubuntu 22.04 GCE host; the VM,
boot disk, uploaded bundle, and temporary SSH metadata were removed after validation. - Test source changes during packaging.
- Test CRLF/LF behavior explicitly and document whether byte identity changes.
- Test executable-mode preservation and Windows's mode limitations.
- Test missing, malformed, and identity-mismatched
SKILL.mdfiles. - Test every size, count, and depth boundary at limit, one below, and one above.
- Test traversal, absolute paths, drive paths, Unicode/case collisions, duplicate paths, all
rejected link and special-file types, and encrypted entries. - Test truncated archives and invalid tar and content checksums.
- Test invalid gzip bytes and map zlib-specific errors to the stable non-retryable archive
category. - Fuzz archive path normalization and envelope parsing with bounded resources.
Phase 2 package gate
- Prove invalid archive classes fail before destination mutation.
- Prove the same source bytes produce the same digest on macOS, Linux, native Windows, and WSL.
- Prove package creation and extraction require no
npx, external Node installation, or
upstream CLI runtime.
3. Implement the local installer transaction
Destination resolution
- Add
src/main/skills/skill-install-destinations.ts. - Resolve global canonical roots as
<host-home>/.agents/skills/<skill-name>on the executing
host. - Resolve workspace canonical roots from runtime-owned worktree or folder-workspace identity.
- Reject client-supplied remote paths that do not resolve to the selected workspace identity.
- Resolve WSL home, paths, and target distro inside the selected distro.
- Keep all path joins platform-native and reject destination escapes after realpath-aware
containment checks. - Support long Windows paths and case-insensitive destination collision checks.
Admission, locking, and ingress
- Add
src/main/skills/skill-package-download.ts. - Add
src/main/skills/skill-install-service.ts. - Validate request shape, schema, capability, package identity, scope, destination, and policy
before download or mutation. - Recover prior journals for the same destination before planning a new operation.
- Acquire a filesystem-backed cross-process lock keyed by canonical destination.
- Bound lock wait time and return a retryable busy result.
- Stream ingress to an owner-private bounded temporary file and hash it while downloading.
- Require HTTPS whenever the configured Cloud endpoint uses HTTPS.
- Allow only configured Orca skill-bucket origins and reject credential-bearing cross-host
redirects. - Check expected compressed bytes, archive SHA-256, package digest, cancellation, grant expiry,
and disconnects. - Delete partial ingress bytes on every incomplete path.
Planning and conflict handling
- Add
src/main/skills/skill-install-planner.ts. - Classify missing canonical destinations as installable.
- Classify an identical requested digest as unchanged and continue placement repair.
- Permit clean updates only when installed bytes still match Orca provenance.
- Return a modified conflict when installed bytes drift from their receipt.
- Return an unowned conflict when a destination exists without Orca provenance.
- Return topology conflicts for files, external links, broken links, and name collisions.
- Classify every requested provider placement independently.
- Repeat current-state inspection immediately before commit and invalidate stale previews.
- Require explicit confirmation before discarding local modifications or unowned content.
Durable commit and recovery
- Add
src/main/skills/skill-install-transaction.ts. - Copy extracted bytes into a hidden sibling staging directory and verify the digest again.
- Preserve executable modes where the host supports them.
- Keep staging, replacement, and backup on the destination filesystem.
- Write and durably flush a versioned transaction journal before moving the current
destination. - Move an approved existing destination to a journal-owned backup.
- Rename verified staging into the canonical path.
- Add bounded retries for Windows
EPERM,EACCES, andEBUSYrename races caused by
antivirus or indexing. - Observe the committed destination and require the requested digest.
- Restore the backup after failed commit and retain sufficient journal state after a crash.
- Never remove a path based only on a filename pattern; require journal ownership,
containment, and expected identities. - Run bounded recovery at startup and before later operations for the destination.
Startup scans at most 64 install and 64 removal journals, bounds each read to 4 MiB,
validates the journal filename against the canonical-path state key, and recovers removal
before install under the destination lock. Dead-PID locks are reclaimed immediately after a
killed runtime; malformed locks retain the bounded stale-age safeguard. Corrupt or conflicting
journals are preserved and reported by path-free failure code for manual recovery.
Provenance
- Add
src/main/skills/skill-install-provenance.ts. - Store versioned receipts outside installed skill directories.
- Record package/version IDs, digest, scope, destination identity, canonical identity,
placements, previous version, timestamp, and runtime identity. - Exclude credentials, grants, share values, manifests, and package contents.
- Serialize receipt updates across processes.
- Write receipt updates through durable temporary-file replacement.
- Support aggregate-index reconstruction from bounded per-install receipts.
- Mark the journal complete only after receipt publication, then remove transaction backups and
staging.
Verification and discovery
- Re-run skill discovery on the destination after commit.
- Verify the canonical skill and successful provider placements are observable.
- Determine success from installed bytes and discovery, not process exit alone.
- Invalidate renderer skill caches and refresh the Skills page.
- Return canonical success with
partialwhen an optional provider placement fails.
4. Implement provider placements
Provider registry
- Add
src/main/skills/skill-provider-destinations.ts. - Add data-only records for Orca agent ID, canonical-root support, global/project resolvers,
detection evidence, and platform alias support. - Populate only provider paths independently verified from official documentation and real
installations. - Detect an agent before creating its provider-specific configuration root.
- Avoid modifying roots for agents that consume
.agents/skillsdirectly. - Keep loose-skill placement as the universal/default installation path.
- Do not add Cursor-specific placement in V1; keep the Agent Plugins-compatible root as the
portable archive shape while installing contained skills through canonical loose-skill paths. - Do not generate
.claude-plugin/plugin.jsonor.codex-plugin/plugin.jsonadapters in V1;
native plugin installation remains a separately reviewed follow-up if it becomes useful. - Use loose skills for plugin clients, including the current Codex IDE extension, and retain
bundle/version provenance for every installed skill.
Placement reconciliation
- Add
src/main/skills/skill-placement-reconciliation.ts. - Create relative directory aliases from real parent directories on POSIX.
- Create directory junctions with absolute canonical targets on Windows.
- Detect provider parents already linked to canonical storage.
- Fall back to an independently copied and verified directory when aliases are unavailable or
denied and policy allows it. - Record each canonical copy, provider alias, junction, or independent-copy topology.
- Reconcile placements idempotently after install and update.
- Repair broken Orca-owned aliases.
- Leave unowned or modified provider placements untouched unless explicitly replaced.
- Preserve canonical success when a provider placement fails and make coverage retryable.
Provider validation
- Confirm every initial provider discovers a global canonical or reconciled install.
- Confirm every initial provider discovers a workspace install only in that workspace.
- Test symlink, junction, and copy fallback behavior after provider and canonical parent paths
are moved or linked. - Test provider release upgrades against the registry through the normal review process.
5. Implement update, rollback, and removal
Update and rollback
- Resolve the latest accessible immutable Cloud version without mutating local state.
- Compare the requested digest with provenance and observe current bytes before offering an
update. - Route clean updates through the same install transaction.
- Offer keep-local, authorized publish-as-new-version, and explicit discard-and-replace choices
for modified installs. - Reconcile recorded aliases, junctions, and independent copies during updates.
- Implement rollback as installation of a selected retained immutable version.
- Never invoke
npx skills updateor write ownership metadata for the community CLI.
Local removal
- Observe the canonical destination and every receipt-owned placement before mutation.
- Remove only aliases or junctions that still target the recorded canonical path.
- Remove independent copies only when they match their receipt unless discard is explicit.
- Remove the canonical copy only when Orca owns it, it is unmodified, and no retained receipt
depends on it. - Publish provenance changes durably before completing removal.
- Leave changed or unowned paths intact and report exactly what remains.
Recovery failure injection
- Inject failure before and after every journal transition.
- Verify partial downloads and extraction staging are deleted.
- Verify a moved destination is restored from backup.
- Verify a placed destination without a receipt is completed or restored according to journal
state. - Verify a published receipt with an incomplete journal is finalized safely.
- Verify a restarted process discovers orphaned install and removal journals without a later
user operation. Deterministic startup tests prove a committed update publishes provenance and
cleans its dead lock, while an interrupted removal restores both bytes and receipt. - Verify interrupted placement reconciliation preserves the canonical install and retries.
- Test cancellation during download, extraction, staging copy, commit, provenance, and
placement reconciliation.
6. Provision GCP infrastructure
Environment and Terraform prerequisites
- Inspect the active
onorca-cloudproject read-only and record the existing Cloud Run, GCS,
Cloud SQL, IAM, and enabled-service baseline. - Locate the authoritative Orca Cloud Terraform worktree, state, modules, and deployment
pipeline. - Confirm or create a separate staging GCP project before exercising new lifecycle and IAM
behavior. - Confirm production names and quotas, including availability of
onorca-cloud-skill-packages. A read-only 2026-08-12 check returned404for the global
bucket name, found no productionorca_skillsdatabase or skill-database secret collision,
and confirmed the intended existing API service and runtime identity. Cloud Run exposes a
1,000-job regional quota and 180 job runs per minute, while V1 reuses the existing API at a
20-instance ceiling and existing Cloud SQL instance; no quota increase is required. - Confirm
USstorage satisfies initial residency requirements. - Confirm Cloud SQL connection and PostgreSQL user provisioning conventions.
- Keep Firestore, Cloud Tasks, and new Pub/Sub dependencies out of V1.
- Declare every durable resource and IAM binding in Terraform; do not provision them manually
withgcloud.
Dedicated GCS bucket
- Declare private bucket
onorca-cloud-skill-packages, subject to Terraform validation. - Set location to
USfor parity with current artifact storage. - Enforce uniform bucket-level access and public-access prevention.
- Configure seven-day soft delete.
- Keep object versioning disabled and record object generations in PostgreSQL.
- Add a one-day deletion lifecycle for
uploads/quarantine objects only. - Add exact approved production and development Orca origins to CORS; never use
*. - Allow only required signed POST upload and GET/HEAD download behavior and response headers.
- Keep the bucket off public custom domains.
- Define immutable final keys as
packages/v1/tenants/<tenant-hash>/sha256//<archive-sha256>/package.tar.gz, with the
logical package digest verified in object metadata and PostgreSQL. - Define tenant-bound random quarantine keys as
uploads//package.tar.gz.
Database and secret
- Declare database
orca_skillson existing regional PostgreSQL 17 instanceorca-cloud-auth-db. - Declare dedicated principal
orca_skills_appwith access only toorca_skills. - Store its connection URL in Secret Manager as
orca-cloud-skills-database-url. - Attach the existing Cloud SQL instance to
orca-cloud-apiwithout replacing the service. - Inject only the skill database secret into the API service.
- Verify backups and point-in-time recovery cover the new database.
orca_skillsshares the
Terraform-managed Cloud SQL instance whose live staging and production settings both have
backups, replication-log archiving, seven retained backups, and seven-day PITR enabled.
IAM
- Grant
[email protected]bucket-scopedroles/storage.objectUser. - Grant the API service account
roles/cloudsql.clientfor the existing instance. - Grant service-account-scoped IAM Credentials
signBlobfor self-signing V4 policies and
URLs. - Grant Secret Manager accessor only for skill-specific secrets.
- Verify bucket IAM contains neither
allUsersnorallAuthenticatedUsers. - Do not grant desktop, remote runtime, or end-user identities direct bucket IAM.
- Do not create or distribute long-lived GCP service-account keys.
- Keep deployment-account permissions unchanged except for reviewed Terraform management
requirements. Skill sharing adds no deployment-account IAM binding: immutable API and
monitor-image updates use its existingroles/run.developer, while storage inventory and
scheduling use separate least-privilege service accounts.
Cloud Run configuration
- Extend
orca-cloud-apiinus-central1; do not create a separate V1 worker service. - Configure bucket, 40 MiB compressed limit, 15-minute upload TTL, five-minute download TTL,
fixed finalize concurrency, and skill database URL. - Reuse existing auth base URL and application CORS configuration.
- Stream validation with fixed buffers under the existing 512 MiB memory limit.
- Add a small per-instance finalization semaphore and retryable
429or503withRetry-Afterwhen saturated. - Preserve current service scaling initially and tune only from measured CPU, latency,
database, and error data. - Define criteria for splitting finalization into a worker service if it harms existing API
traffic. - Make immutable API candidates atomically replace the complete Terraform-owned literal
environment and the exactORCA_SKILLS_DATABASE_URLSecret Manager reference. - Reject serving or candidate drift in the runtime service account, Cloud SQL attachment,
scaling, CPU, memory, volumes, mounts, ports, and probes before moving traffic. - Keep all four skill controls explicit in both deploy workflows. Staging changed to
true
only with the authenticated OIDC smoke gate; production remainsfalse.
Terraform review and apply
- Format and validate Terraform.
- Produce and review a staging plan.
- Verify the plan does not replace the existing artifact bucket, Cloud SQL instance, Cloud Run
services, or unrelated IAM. - Apply to staging and capture resource and IAM verification evidence.
- Produce and review the production plan after staging gates pass.
- Apply the approved production plan only during the rollout phase.
- Verify production with read-only bucket, Cloud Run, database, IAM, lifecycle, and CORS
commands without printing secret payloads or signed values.
7. Implement Cloud metadata and APIs
PostgreSQL migrations
- Serialize concurrent Cloud Run startup migrations with a transaction-scoped PostgreSQL
advisory lock before migration-table inspection. - Prove failed DDL records no schema version and eight concurrent callers converge on one
version against real PostgreSQL 16 and 17. - Add
skill_packageswith owner organization, slug/name, creator, timestamps, and deletion
state. - Add immutable
skill_package_versionswith package and archive identities, GCS key and
generation, sizes, manifest, release notes, creator, and publication state. - Add tenant/user-bound
skill_package_uploadswith quarantine identity, expiry,
finalization state, and failure category. - Add
skill_package_aclfor organization and user principals and permissions. - Add unpredictable, revocable, optionally version-pinned
skill_share_records. - Add bounded
skill_package_audit_eventswithout contents, filenames, or signed values. - Add unique package slug per organization, immutable version and digest constraints, and
tenant-scoped archive and final GCS key/generation constraints. - Add active-share, ACL-principal, and pending-upload expiration indexes. The ACL index remains
legacy storage and is not used for bearer resolution. - Add foreign keys preventing blob-reference deletion while a published version uses it.
- Accept and stream-validate both the pre-release single-skill envelope and the new Agent
Plugins skills-only bundle envelope during the desktop migration. - Store the detailed bundle manifest in the existing immutable version record; expose it only
after exact-link bearer authorization or to owners. - Keep contained skills out of separate database rows and search indexes. Selective inspection
reads the validated manifest attached to the exact immutable version. - Keep detailed file identity in the in-archive manifest rather than duplicating it into GCS
custom metadata. Published-object metadata contains only aggregate archive/package digests
and immutable generation identity; per-file paths, sizes, modes, and hashes remain inside the
authorized archive manifest and immutable PostgreSQL version record. - Test forward migration, rollback strategy, backup restoration, and migration compatibility
during mixed API versions. Unit coverage proves atomic forward/idempotent migration, ignores
an additive schema version written by a newer API, and leaves no recorded or partial schema
after failure. PostgreSQL 16/17 integration coverage proves transactional DDL rollback and
eight concurrent startup callers converge on one version. Cloud PR#349additionally proves
the older runner and its existing SQL preserve and operate beside a simulated newer additive
column/table. Cloud PR#350requires real old/new Node-process coexistence, writes before
and after apg_dump/pg_restore, and full cleanup on disposable PostgreSQL 16 and 17; both
PR and merged-main runs passed.
Authorization and lifecycle model
- Allow share resolution and share-scoped download grants without an authenticated principal.
- Treat an unpredictable active, unexpired share ID as the bearer authorization; do not apply
legacy package ACLs to recipients. - Return the same non-disclosing
404for missing, expired, revoked, or deleted shares. - Keep authenticated owner checks on upload, finalize, package/version management, share
creation, revocation, and deletion. - Apply distributed per-IP abuse limits to anonymous resolution and grant requests without
persisting raw requester addresses. - Apply organization retention and legal deletion rules over product rollback retention.
- Make package deletion revoke shares before dereferencing immutable objects.
- Delete an object only after a transaction proves no retained version references it.
- Design metadata/object reconciliation for partial publication and deletion failures.
Upload and finalization APIs
- Implement
POST /v1/skill-packages/uploadswith existing authentication, organization
membership, quota, rate, and concurrency checks. - Insert a single-use pending upload before issuing a grant.
- Create a 15-minute V4 signed POST policy bound to exact key, content type, upload ID, expected
archive SHA-256 metadata, and 40 MiB length range. - Upload directly from the client to GCS without routing package bytes through Cloud Run.
- Implement idempotent finalize by upload ID and manifest identity.
- Validate GCS key, size, type, metadata, tenant, generation, and expiry before streaming.
- Stream once to calculate archive SHA-256 and validate envelope, manifest, paths, limits, and
package digest. - Promote with an if-absent generation precondition to the tenant-scoped,
content-addressed final key. - If the tenant's final key exists, verify recorded identity without exposing cross-tenant
package existence. - Publish the version and complete the upload in a PostgreSQL transaction.
- Delete quarantine bytes after success and rely on lifecycle cleanup for abandonment.
- Make all mutating endpoints accept idempotency keys.
Package, version, and share APIs
- Implement package creation and version publication under a stable package identity.
- Implement package details and paginated version history.
- Stop accepting organization/selected-user audience fields for new bearer shares; retain
legacy ACL storage only as migration-compatible data. - Implement durable share creation with optional pinned version and expiry.
- Implement anonymous bearer-link metadata preview.
- Implement immediate share revocation for new grant requests.
- Implement Cloud package/version deletion with retention and reference checks.
- Implement version update lookup and rollback selection.
Download grants
- Implement anonymous
POST /v1/skill-shares/<share-id>/download-grantsafter fresh active-link
evaluation. - Generate a five-minute V4 signed GET URL for the exact immutable key and stored generation.
- Set only response content type and safe attachment filename overrides.
- Grant no list or write capability.
- Return expected archive identity and byte count beside the grant for runtime verification.
- Test revocation immediately blocks new grants while already issued URLs expire within five
minutes.
Cloud tests
- Test anonymous latest/pinned preview and download grants without an Authorization header.
- Test missing, expired, revoked, and deleted shares all fail without disclosure.
- Test authenticated owner management remains protected.
- Test anonymous per-IP rate limiting without recording raw IP addresses.
- Test the authenticated owner-only active-link inventory and ensure responses are not cached.
- Test expired, reused, wrong-tenant, wrong-key, wrong-size, and wrong-hash uploads.
- Test malformed, oversized, and resource-exhausting archives during finalization.
- Test finalization semaphore saturation and retry behavior.
- Test concurrent idempotent mutations and partial database/GCS failures.
- Test tenant-scoped deduplication without cross-tenant timing or response disclosure. Orca
Cloudddbd4e2proves identical archives use distinct tenant-hashed GCS keys and independent
PostgreSQL object identities. - Test quota and rate limits.
- Test durable shares resolving intended latest and pinned immutable versions.
- Test update, rollback, revocation, deletion, soft-delete recovery, and orphan reconciliation.
8. Implement desktop Cloud client and UX
Cloud client
- Make share resolution and share-scoped download-grant calls anonymous while keeping upload,
package/version management, share creation, revocation, and deletion authenticated. - Keep desktop contracts storage-provider-neutral.
- Implement bounded progress, cancellation, retry, and idempotency behavior.
- Redact signed policies, URLs, credentials, private paths, and package contents at creation.
Share experience
- Read and apply
docs/STYLEGUIDE.md; use canonical CSS tokens and shadcn primitives. - Add Share skill to eligible installed and workspace skills.
- Show name, description, author, file count, total size, scripts, executable files, and the
unlisted-link warning before upload. - Remove organization and selected-person audience controls from bearer-link publishing.
- Accept optional release label and notes.
- Package the exact previewed bytes and invalidate preview after source drift.
- Show bounded upload and finalization progress with cancellation.
- Return a copyable durable Orca URL after publication.
- Remove access editing from the bearer-share workflow; keep version publishing, revoke, and
package deletion actions. - Make clear that unsharing blocks future installs but does not remove installed copies.
- Add Share skills, Install from link, and installed-package Manage to the existing
Skills page header; expose active link copy/revocation in Settings → Share Skills so links
remain manageable even when the source was never a managed install. - Add selection mode whose selection survives search/filter changes and an explicit Select
all results action; never select all implicitly. - Keep the per-card share action as a one-skill bundle shortcut.
- Disable ineligible skills with visible, screen-reader-linked reasons and block duplicate
skill names before review. - Review bundle name, skill/file/byte counts, script/executable warnings, expandable per-skill
details, unlisted-link behavior, and release notes. - Add Settings → Share Skills with artifact-like explanation and a route to the Skills page.
- Show named preparation, upload, verification, and link-publication progress and return one
durable link for the bundle. Preparation uses Preparing preview…; publication advances
through Uploading…, Verifying package…, and Publishing link… before rendering the
single durable share URL. Cancellation reaches both upload and link publication.
Install experience
- Show description, version, digest, file summary, scripts, and executable files without
exposing internal publisher or organization identifiers. - Treat the package as code from its author and require an explicit install action.
- Show current-machine global installation as the default.
- Allow selection of connected machine, Git worktree, or plain folder workspace.
- Show detected-agent coverage and canonical/provider topology before commit.
- Show installed-state conflicts and explicit resolution choices.
- Show phase progress without exposing grants or local private paths.
- Render installed, unchanged, updated, partial, conflict, unsupported, cancelled, and failed
results with actionable recovery. - Add incomplete-coverage retry.
- Open deep links in inspection mode without starting installation.
- Show immutable version, skill count, scripts/executables, and release notes before selection;
V1 omits publisher identity from the public response and recipient UI. - Let the recipient select all or a subset, then choose local, paired runtime, WSL, or SSH and
global or workspace scope. - Preview new, unchanged, update, and conflict counts and resolve all per-skill conflicts in one
surface with Keep local as the default. - Label the primary action Install N skills and show aggregate progress plus current skill.
The destination-owned installer emits the current name and one-based bundle position. Local
installs forward it directly; paired and SSH hosts expose a capability-gated read-only
progress method, with phase-only fallback for mixed-version older hosts. - Group results into installed, unchanged, kept local, and failed; retry failed items only.
Skills page lifecycle
- Show installed package/version identity and update availability.
- Offer clean update, modified-copy choices, prior-version rollback, and safe local removal.
- Show whether a package came from Orca Cloud and its accessible version history.
- Refresh discovery and installation state after local or remote actions.
- Keep Cloud deletion, share revocation, and local removal clearly separate.
- Add installed-bundle update, rollback, install-on-another-machine, inspect, and remove actions.
An active unlisted link opens the existing destination-aware installer for another machine. - Add shared-bundle copy-link, access, revoke, publish-version, and Cloud-delete actions. Exact
full-bundle version publishing reuses its package; ambiguous or partial managed-bundle
selections create a new package rather than mutating the wrong history. - Protect locally modified skills during bundle update and removal, including when only part of
a bundle remains managed. Keep-local is the default and destructive replacement is explicit.
UX validation
- Test keyboard shortcuts and labels with platform-aware macOS versus Windows/Linux behavior.
Skill sharing adds no modifier shortcut or shortcut label. Renderer coverage proves its
platform-neutral Escape navigation closes the page, leaves editable controls alone, and
remains owned by open dialogs; the compatibility scan found no hardcoded Meta-only binding,
macOS glyph, or platform-specific accelerator in the feature. - Test loading, error, cancellation, partial, conflict, and stale-preview states. Renderer
coverage includes scan loading, unavailable links, upload/install cancellation, partial
provider results, keep-local conflicts, capability loss after preview, and selection
reconciliation after a changed discovery scan; package tests cover source drift. - Test screen-reader names, focus order, progress announcements, and destructive confirmations.
The link field receives initial focus, Tab reaches the enabled primary action, Escape closes
the dialog, and machine, destination, WSL, and workspace selectors have programmatic names.
Upload progress exposes its phase and numeric value from 0 through publication; dialog-level
install tests announce authorization, installation, and current-skill position. Link revoke,
Cloud deletion, and local removal retain focus while switching to exact confirmation names. - Test long names, release notes, paths, organization names, and localized copy. Renderer
coverage exercises the 64-character bundle/skill-name boundary, a 10,000-character multiline
release note, long Unicode descriptions and organization IDs, long Windows-style skill paths,
and long workspace labels. Review copy wraps without losing full values, selectors constrain
labels to dialog width, and localization catalog, extraction, and coverage gates pass. - Review trust and privacy wording with security and design.
9. Implement paired runtime installation
Runtime and IPC surface
- Register preview, install, update, and remove through runtime RPC and desktop IPC.
- Add bounded
beginUpload,uploadChunk,commitUpload, andcancelUploadmethods. - Route every request to the runtime that owns the selected host and workspace.
- Keep transfer separate from installation so grants, relayed chunks, and local files converge
at the validated ingress boundary. - Make upload sessions bounded by count, total bytes, idle lifetime, and chunk size.
- Require monotonic offsets and idempotent acknowledged-chunk retry or full restart. A stable
transfer ID and host-acknowledged offset resume a lost begin response or interrupted upload. - Release staging bytes after cancellation, disconnect, timeout, and runtime restart. Active
idle timers remove abandoned archives even when no later upload arrives.
Direct and client-mediated transfer
- Prefer destination-runtime direct download when it can reach approved Orca Cloud storage.
- Verify origin, redirects, generation, byte count, archive hash, and package digest on the
destination runtime. - Fall back to authenticated client-mediated chunk transfer when the host cannot reach GCS.
- Never pass the desktop's local path as a remote package source.
- Preserve the same inspection, transaction, provenance, and result behavior for both
transports. - Retry an idempotent install after a recoverable lost final response; rebuild a staged
transfer before retrying so a consumed upload is never reused. - Run the real Electron client against a paired desktop host for global, Git-worktree, and
plain-folder install, unchanged preview, inventory, and removal. - Run the same contract against a paired headless
orca servehost for global and
Git-worktree install, unchanged preview, inventory, and removal. - Verify paired installation creates files only in host-owned roots and never falls back to the
client's isolated home. - Test connection loss and resumption or restart behavior at every transfer boundary. Lost
final install-response convergence is covered for direct and staged paired-runtime paths;
deterministic cancellation now settles in-flight and queued paired calls across every
transport without leaking request admission or disrupting peers. Client-mediated transfer
also cancels and cleans a fully uploaded session when cancellation interrupts the commit RPC.
Restart and the remaining physical disconnect boundaries are still open.
Mixed versions
- Advertise additive bundle installation separately on paired and SSH hosts; never send the
bundle method to an older peer that lacksskills.install.bundle.v1. - Hide or disable remote install when
skills.install.v1is absent. - Show a specific host-update-required message instead of attempting the RPC.
- Test new client/old server and old client/new server.
- Test optional-field omission in both directions.
- Test a capability changing after preview but before execution.
- Confirm older servers retain skill discovery and ignore no unknown stream opcode because none
is introduced.
10. Validate native Windows and WSL on `windows 2`
The connected Orca environment windows 2 was reachable on 2026-08-11 and ran Orca 1.4.180 with
runtime ID 68b5e70d-baaf-40a5-b384-be09cc088880. Validation used the isolated checkoutC:\Users\neil\orca\skills-share-validation and WSL distro Ubuntu-24.04; no production skill
directory was used for destructive failure injection.
The final combined run used workspace skills-share-staging-validation atC:\Users\neil\orca\workspaces\orca\skills-share-staging-validation on branchOrcaWin/skills-share-staging-validation. At 44d1266641, native-Windows, real-process, and real
WSL coverage ran together: 67 files passed, 449 tests passed, and 17 intentional platform skips
remained. The Windows checkout was clean afterward, and WSL /tmp contained no remainingorca-skill-* directory.
The 2026-08-11 host inventory recorded Windows 11 Pro 10.0.26200 build 26200, x64, healthy
NTFS, Windows Defender, LongPathsEnabled=0, no Developer Mode registry grant, and an expectedUnauthorizedAccessException for an unprivileged directory-symlink probe. The current user home
is C:\Users\neil and the temp root is under that profile. WSL 2.7.11.0 uses kernel6.18.33.2-microsoft-standard-WSL2; its only installed distro is the running defaultUbuntu-24.04 WSL2 instance, with default user neil, home /home/neil, x86_64, and an ext-family
distro filesystem. No standalone Orca CLI is installed inside that distro; WSL skill operations
are owned by the connected Windows Orca runtime. A reproducible second-distro setup iswsl --install -d Debian --no-launch, followed by first launch and an isolated Orca test user/home
record before the multi-distro gate.
At commit 1e2d2e806f, the opt-in real-Windows workspace harness created an actual Git linked
worktree and a plain non-Git folder under owner-controlled temporary roots containing spaces and
non-ASCII characters. It installed globally and into both workspace kinds through host-owned IDs,
then removed the complete fixture tree. The full native src/main/skills slice passed 284 tests
with 21 intentional platform skips across 43 files, and the Node typecheck passed. Two discovery
fixtures uncovered by that run now use Windows junctions instead of privileged directory symlinks,
matching Orca's supported Windows placement topology.
At commit cdafe43542, the opt-in WSL semantics harness used real provider detection and installed
through the selected distro after converting UNC verification paths to distro-native paths. The
run passed both cases: Linux case sensitivity was preserved, regular files were 0600, executable
files were 0700, and the packaged script executed successfully. Existing real transaction
coverage also verified POSIX provider alias creation inside the distro.
At commit 92c8c6a4c6, the exact portable package golden passed 26/26 focused tests from a native
WSL filesystem using an isolated user-owned Node 24 toolchain. The package digest and archive
SHA-256 matched macOS, native Windows, and Linux container results; the temporary WSL toolchain,
source snapshot, dependencies, and package-manager store were removed after the run.
Commit 97b831dd17 stabilized the physical Windows recovery harness by asserting parsed JSON paths
instead of shell-escaped text and by using a fake clock for upload ownership expiry. The combined
run then exposed a real WSL placement bug: native UNC lstat could miss an existing distro symlink,
so GNU ln -s followed it and created a self-link inside the canonical skill. Commit 44d1266641
now asks the WSL filesystem whether the alias already targets the canonical path before native
existence checks and uses ln -sT for safe creation.
Only Ubuntu-24.04 is installed on this host. A second-distro permutation is a post-launch
resilience follow-up, not a first-release gate. On 2026-08-12, the owner accepted the additional
live WSL-to-staging journey as duplicate evidence after the physical Windows/WSL matrix and native
Windows Cloud lifecycle passed; the remaining disconnect/cancellation boundaries stay recorded as
residual follow-up work.
Host preparation
- Confirm
windows 2is saved and reachable throughorca environment listandorca status --environment "windows 2" --json. - Confirm the host provides both a native Git checkout and a native plain folder workspace.
- Record Windows edition/build, architecture, filesystem type, long-path policy, current user
home, temp root, antivirus status, and developer-mode/symlink policy without collecting
secrets. - Discover installed WSL version and distros, each distro's running state, default user, home,
filesystem, and Orca host support. - Ensure at least two WSL distros are available or record a reproducible second-distro setup
for the multi-distro test. - Create isolated test skills and destination roots under explicit test workspaces; never use
production user skill directories for destructive failure injection. - Record exact Orca client/server versions and capabilities with each test run.
Native Windows package and destination tests
- Package on macOS and install on native Windows; compare manifest and digest. The physical
staging journey installed the macOS-published archive with the exact published digest, and
the portable golden independently matched manifest, package digest, and archive SHA-256. - Package on native Windows and install on macOS/Linux; compare identity and executable-mode
policy. - Test global home resolution without constructing the path on the macOS client.
- Test a host-owned real Git worktree and plain folder workspace independently.
- Test path joining, drive prefixes, UNC rejection/handling policy, reserved names, trailing
dots/spaces, long paths, Unicode normalization, and case collisions. - Test source and destination paths containing spaces and non-ASCII characters.
- Test junction creation succeeds for a detected provider.
- Force junction denial and verify an independently copied, digest-verified fallback.
- Test an unowned junction, external link, broken junction, and provider parent that is already
linked. All four real NTFS junction cases passed onwindows 2atc7c8a2e32d; Orca repairs
only its receipt-owned broken junction and preserves unowned links. - Test copy-fallback drift during update and removal. Real Windows coverage at
b4f5b6c119
proves a locally modified independent copy is skipped during reconciliation and refused
during removal without changing its bytes. - Hold destination files open to simulate antivirus/indexer contention and verify bounded
EPERM,EACCES, andEBUSYrename retries. A realFileShare.Nonelock onwindows 2
proved transient recovery and bounded exhaustion at18fe31f107. - Verify retry exhaustion restores the old version and yields an actionable result. The
transaction preserves the old bytes and returns retryableskill-install-filesystem-failed; the real Windows source remains intact for retry. - Terminate the runtime before and after each journal boundary and verify startup recovery.
The combined physical Windows run passed the real-process extraction, install, removal, and
upload restart suites, including begun, partial, fully uploaded, and committed upload states. - Test permission-denied, read-only, disk-full, cancellation, runtime disconnect, and partial
provider-coverage paths. DeterministicEACCESandENOSPCtransaction injection now proves
the prior installed version remains intact; physical read-only/disk-full and disconnect
journeys remain open.
WSL tests
- Execute package ingress, extraction, home resolution, installation, provenance, and discovery
inside the selected distro. - Prove the Windows client never constructs a Linux home or mutates the distro through a
translated Windows path. - Post-launch: test global installs for a second distro with a different default user/home.
- Test Linux case sensitivity and executable modes inside the installed distro. Real
Ubuntu-24.04coverage passed atcdafe43542with0600regular files and0700
executable files. - Install a multi-skill bundle selectively into a distro-owned home and prove unselected skills
are absent. The firstwindows 2run found missing filesystem preparation; the fix at70b847c38epassed all 3 realUbuntu-24.04tests and the temporary worktree was removed. - Post-launch: repeat Linux case-sensitivity and executable-mode coverage in a second distro.
- Test a distro-owned Git worktree and plain folder workspace.
- Test a workspace on the distro filesystem and document behavior for
/mnt/cseparately. - Test provider detection and POSIX alias creation inside the distro. Real
Ubuntu-24.04
provider detection passed atcdafe43542; the transaction harness also verified the Claude
placement as a distro-owned POSIX symlink. - Test concurrent native Windows and WSL installs of the same package without provenance or
lock collision across host identities. - Test distro stopped, distro removed, default user changed, home moved, and runtime restarted
between preview and install. - Block outbound GCS access inside WSL and verify client-mediated chunk transfer.
- Disconnect the macOS client during transfer and commit; verify bounded cleanup and recovery.
Windows/WSL mixed-version tests
- Use
windows 2as old host with a newer client and verify capability-gated UI. - Upgrade
windows 2only through the normal supported update path, then test an older client
against the newer host. - Verify folder-workspace IDs, runtime-owned paths, and results survive client/host version
skew. - Capture structured test evidence without signed URLs, tokens, skill contents, or private
absolute paths beyond approved test fixtures.
Windows/WSL release gate
- Real native Windows passes junction success, junction denial, verified copy fallback,
antivirus contention, long paths, crash recovery, Git worktree, and folder-workspace tests.
The final combined run passed 449 tests across 67 files at44d1266641. - Real WSL passes distro-owned paths, Linux semantics, provider placement, cancellation, and
crash recovery tests. Distro-owned paths, semantics, provider placement, and interrupted
update recovery passed in the combined physical matrix. The owner accepted the additional
live WSL-to-staging journey as duplicate release evidence on 2026-08-12; physical
disconnect/cancellation injection remains open, and a second distro is post-launch. - Temporary local test installations, workspaces, and packages are removed through verified,
recoverable cleanup. The final checkout was clean and WSL/tmphad noorca-skill-*debris.
11. Implement and validate SSH targets
The 2026-08-11 live Orca inventory contained only the connected windows 2 environment and no
non-local worktree or repository. Later staging journeys registered disposable Ubuntu 20.04 and
native macOS ARM64 hosts directly through Orca's SSH provider and removed them afterward. A
supported Windows SSH target remains required for its platform-specific gate.
The Docker-backed Linux SSH harness exercises the production Electron → SSH provider → relay →
remote installer path. Its loopback package origin is unreachable from the container, forcing
client-mediated chunk upload. The test independently verifies Cloud request receipts and remote
files for global, Git-worktree, and plain-folder installs, unchanged preview, listing, and removal.
Physical staging journeys additionally passed the supported Ubuntu 20.04/glibc 2.31 floor and
native macOS ARM64; supported Windows remains open.
- Define a host-side installer command that invokes the same installer core and structured
result contract. - Transfer immutable packages through the existing SSH/SFTP provider into bounded private
staging. - Invoke installation on the SSH host so it owns home, path, provider, and filesystem
resolution. - Add capability/version detection for the required host component.
- If unsupported, offer a package download or explicit command; never install into the
desktop's home as fallback. - Propagate cancellation and clean partial SSH transfers and staging.
- Route selective bundle installs through an additive SSH method with direct download,
client-mediated fallback, per-skill results, and old-host capability fencing. - Test SSH-only macOS, Linux at the supported floor, and Windows where the existing provider
supports it. Ubuntu 20.04/glibc 2.31 and native macOS ARM64 passed the full live staging
lifecycle; supported Windows remains open. - Test Git worktree and folder-workspace scope over SSH through the disposable Docker Linux
target and verify the host-owned paths independently. - Test connection loss during upload, extraction, commit, provenance, and result return. Lost
result-return recovery is covered for direct and staged SSH installs; the other physical
boundaries remain open. - Prove SSH and paired runtimes return the same result and error-category contracts. The full
nine-category matrix now passes through both transports with exact code and retryability
preservation. This exposed and fixed SSH's numeric JSON-RPC code taking precedence over its
validated structured skill failure; arbitrary error data remains rejected by the strict
schema.
12. Add observability, operations, and security controls
Metrics and dashboards
- Record bounded package byte/file counts and package, upload, finalization, download, transfer,
install, placement, and recovery durations. - Record outcomes by error category, OS, destination kind, transport, conflict type, and
placement topology. - Record junction, alias, copy-fallback, rollback, recovery, capability-absence, and orphan
reconciliation counts. - Add dashboards for grant/finalize/share rates, authorization and rate limits, finalization
saturation, archive rejection, and digest mismatch. - Add Cloud Run 5xx, CPU, memory, instance, and skill-route latency panels and alerts.
- Add GCS quarantine/published bytes, object count, and lifecycle failure panels and alerts.
- Add PostgreSQL connection, storage, query latency, migration, and transaction panels and
alerts. - Add signed-policy/URL generation and IAM Credentials failure alerts. Grant-signing failures
are mapped to a stable application category so the alert does not depend on project-wide
Data Access audit logging; evidence is in Orca Cloud69b388e. - Add budget alerts for GCS storage/egress, Cloud Run growth, and Cloud SQL storage.
Dashboard evidence in Orca Cloud 41ef335 and f67ee2c: upload-grant, finalize, share, and
download-grant success; bounded security failures; Cloud Run CPU, memory, instances, and latency;
GCS bytes and object count; and Cloud SQL connections, disk use, p99 skill-principal query latency,
and skill-database transaction rate. Cloud PR #344 passed both required checks and merged asfcf8655a; it adds privacy-bounded migration-ready/failure metrics, a migration lifecycle panel,
and a zero-tolerance migration-failure alert. Its targeted staging plan creates the two metrics and
alert, updates only the dashboard, reruns descriptor propagation, and destroys no infrastructure.
Cloud PR #345 passed both required checks and merged as 06a5c729; it normalizes malformed
compression failures to the bounded skill_package_archive_invalid category, adds explicit
finalization-saturation and archive-rejection metrics, and displays successful and failed API
operations as rates. The broader unchecked items still require the listed split metrics, lifecycle
visibility, budget coverage, and reviewed alert thresholds. Cloud PR #346 passed both required
checks and merged as 8199c048; it adds privacy-bounded per-route latency distributions, sustained
80% CPU/memory and near-instance-ceiling alerts, separate five-second interactive and 30-second
finalization p99 latency alerts, and a route-template p99 dashboard without bearer identifiers.
Cloud PR #347 passed both required checks and merged as 65320475; it adds a six-hour
aggregate-only storage inventory job, fixed quarantine/published bytes and object-count metrics,
an overdue-quarantine metric, two storage panels, and an overdue-or-job-failure alert. The monitor
identity has only storage.objects.list, while the scheduler can invoke only that job. The reviewed
targeted staging apply added 18 observability resources, updated the existing dashboard, and
replaced only the inert descriptor-propagation timer. A manual run emitted only aggregate fixed-
namespace measurements with zero overdue objects; the post-apply targeted plan is zero-diff, SQL
remained NEVER/STOPPED, all Relay MIG targets remained zero, and production remained disabled.
Desktop operations now emit local diagnostic spans for package creation, direct upload,
finalization, grant download, client-mediated runtime/SSH transfer, single and bundle install,
provider placement, transaction rollback settlement, and startup recovery. Package bytes/files,
OS, destination kind, transport, conflicts, placement topology/mechanism, copy fallback,
capability absence, recovered transactions, stale extraction/lock cleanup, and failure categories
use fixed or bounded values; bundle error-category cardinality is capped at 32. The implementation
adds no product telemetry event, RPC field, persisted schema, or remote opcode. The skill-sharing
release suite passes 427 tests with 29 expected platform skips, the focused affected matrix passes
102 tests with one expected skip, typecheck and the full lint pipeline pass, and the real-process
macOS recovery matrix passes all 17 crash boundaries. The final combined physical Windows/WSL run
passes 449 tests across 67 files with 17 intentional platform skips; live WSL staging and physical
disconnect boundaries remain separate gates below.
Logging and privacy
- Log package/version IDs, phases, destination labels, placement outcomes, and bounded error
categories only. - Exclude package contents, filenames, manifests, raw local paths, ACL membership, durable share
URLs, upload policies, download grants, and credentials. - Redact sensitive network values before logger invocation, not after ingestion.
- Audit authorization and lifecycle events without instruction or script contents.
- Verify diagnostics and support bundles preserve the same exclusions. Desktop install spans
map requests and results to bounded labels before the tracer boundary; regression coverage
proves raw paths, filenames, manifests, ACL values, durable share URLs, upload policies,
download grants, credentials, and raw error text never enter the collected bundle.
Security review
- Threat-model package creation, archive ingestion, manifest trust, path containment, local
conflicts, grants, redirects/SSRF, authorization, tenant isolation, and instruction/script
trust indocs/reference/agent-skill-sharing-threat-model.md; keep approval and residual-risk
acceptance as separate human gates. - Verify owner-private staging permissions on supported hosts. POSIX download, package,
extraction, upload-session, provenance, and lock tests require0700directories and0600
files; real WSL verifies owner-private executable modes; and the native-Windows privacy suite
verifies staging remains under the owning profile without broad ACL disclosure. - Rate-limit uploads, finalization, downloads, share resolution, and remote transfer sessions.
- Test malicious redirects, DNS/host confusion, expired grants, mismatched generations, and
oversized streaming bodies. Exact HTTPS origins, manual same-origin redirects, URL
credentials, suffix/port confusion, streamed byte bounds, expiry-before-fetch, and immutable
GCS generation pinning are covered in Orca534abb8908and Orca Cloud81a581d. - Test archive and filesystem race conditions, including source drift and destination changes
after preview. Package creation re-observes staged bytes, and install commit re-observes the
locked canonical destination before its first destructive rename. - Review organization departure, deletion, retention, soft-delete, and operator recovery.
- Complete privacy and security sign-off before external rollout.
Runbooks and kill switches
- Add independent server-side flags for upload grants, download grants, and remote
installation. - Verify disabling Cloud grants does not break skill discovery or already installed skills.
- Document finalization saturation, GCS/IAM signing failure, database outage, corrupt package,
orphan reconciliation, and rollback response. - Document coordinated PostgreSQL point-in-time and GCS generation/soft-delete restoration.
- Document package/share deletion, organization departure, legal retention, and audit handling.
- Document how and when to split finalization into its own service.
13. Complete automated and end-to-end validation
Transaction matrix
- Test fresh install, identical reinstall, clean update, rollback, explicit replacement, and
safe removal. - Test modified and unowned canonical and provider conflicts.
- Test canonical/provider paths as regular files, directories, aliases, junctions, external
links, and broken links. Deterministic native coverage now preserves canonical regular-file
name collisions, unowned provider files/directories, external and broken unowned links,
Orca-owned broken-link repair, aliases, and verified copy fallback. Physical Windows
junction coverage remains open. - Test concurrent desktop, headless runtime, CLI, SSH, and recovery attempts.
The shared transaction core now serializes two simultaneous installs into one installed and
one unchanged result, one receipt, and no staging residue. The host request service also
serializes simultaneous download-grant and trusted-local ingress requests with the same
guarantees. A real multi-process harness proves an on-disk destination lock returns a
retryable busy result without residue, then converges to unchanged after the owning process
commits and releases; the release gate runs it on macOS, native Windows, and Ubuntu 20.04.
Named desktop/headless/CLI/SSH/recovery process combinations remain open. - Test permission and read-only failures, disk exhaustion, cancellation, process termination,
and host disconnect. SimulatedEACCESandENOSPCpreservation plus lost-response retry are
covered; physical failure and termination journeys remain open. - Test independent-copy drift and incomplete provider coverage. Deterministic transaction
coverage preserves modified independent copies, retains canonical success when an unowned,
failed, or cancelled provider placement cannot be reconciled, reports the exact partial
placement outcome, and repairs interrupted coverage on retry.
Platform and target matrix
- Run macOS ARM64 and x64 behavior where supported.
- Run Linux against Ubuntu 20.04/glibc 2.31 and verify bundled native binaries respect the
floor. The exact x64 directory package passed its 18-binary ABI scan and packagednode-ptyload/spawn smoke inside Ubuntu 20.04 atf1dccb4f42. The exact ARM64 directory
package atabe92d565bpassed the same 18-binary floor scan and emittedorca-node-pty-floor-okfrom an ARM64 Ubuntu 20.04 container on nativeaarch64hardware. - Run native Windows and WSL scenarios on
windows 2. - Run local and remote Git worktrees and plain folder workspaces. Native Windows covered a
linked worktree and folder; paired desktop and Docker SSH covered host-owned worktrees and
folders; physical Ubuntu SSH covered the global lifecycle. - Run same-build paired desktop and headless runtimes through client-mediated transfer; headed
coverage includes global, Git-worktree, and plain-folder scopes, while headless coverage
includes global and Git-worktree scopes. - Run paired runtimes with both client-newer and server-newer combinations.
- Run supported SSH-only macOS, Linux, and Windows targets. Native macOS ARM64 and Ubuntu
20.04/glibc 2.31 passed full staging lifecycles; supported Windows remains open. - Run a remote target without outbound Cloud connectivity through chunk transfer. The Docker
SSH target could not reach the loopback package origin and completed global, worktree, and
folder installation through client-mediated upload.
Required journeys
- Share on machine A, install globally on machine B, launch a detected agent, and discover the
skill. - Share on machine A, install into a folder workspace on a connected runtime, and discover it
only in that workspace. - Modify installed bytes, publish an update, and prove Orca refuses silent replacement. The
browser-free macOS staging lifecycle preserved the modified v1 copy before applying v2 only
after the explicit test decision. - Disconnect during commit, reconnect, recover, and prove either the old or new version is
complete. - Revoke a share, prove a new install fails, and prove the existing local install remains.
Bearer-route tests and staging smoke reject revoked resolution/grants; macOS, native Windows,
paired, and SSH lifecycles independently preserve already installed bytes. - Publish a new immutable version, update one machine, leave another pinned, then rollback the
updated machine. The physical Windows staging journey updated only the remote host to v2,
retained the local machine's independent version, and rolled Windows back to v1. - Remove local installation without deleting its Cloud package, then delete/revoke Cloud
access without mutating another local installation. Staging lifecycles separate host/local
removal from package deletion, and revocation never mutates either installed copy.
CI and test evidence
- Add deterministic fixtures authored for Orca without copying upstream fixtures.
- Add failure-injection hooks available only to tests/development harnesses.
- Add a CLI-only developer harness for local and remote integration tests.
- Keep CI commands compatible with macOS, Linux, Windows PowerShell/cmd, and WSL.
- Archive bounded test results and security evidence without secrets or private contents. The
required release jobs archive bounded machine-readable results; privacy-safe Cloud field
inventories, load aggregates, and recovery evidence contain no grants or package contents. - Make package safety, transaction recovery, platform, and mixed-version suites required release
checks. Release publication now depends on focused macOS, real native-Windows, and Ubuntu
20.04/glibc 2.31 jobs that archive bounded machine-readable results.
14. Stage and release
Staging
- Apply reviewed Terraform and migrations to staging.
- Deploy
orca-cloud-apiskill routes disabled by server-side flags. - Enable all four staging controls and pass the GitHub OIDC owner plus anonymous bearer-link
lifecycle smoke, including owner management, immutable versions, local/remote grants,
rollback selection, expiry, revocation, deletion, and object cleanup. - Run staging upload/finalize/download smoke for anonymous denial, oversize, corruption,
expiry, revocation, deletion, and cleanup; retain tenant-deduplication coverage in the API
integration suite. - Run desktop-to-local, paired-runtime,
windows 2, WSL-equivalent host ingress, and SSH
journeys in staging.
Browser-free macOS run31569902499passed publish, install, conflict preservation, update,
rollback, revocation, installed-copy preservation, removal, and Cloud deletion. Physical
native Windows passed the same lifecycle through run31591275227, including remote-owned
path and digest verification with no macOS fallback. A disposable Ubuntu 20.04/glibc 2.31
host passed the same SSH lifecycle after guarded wake31603249983; guarded sleep31604391897restored the low-cost state. An isolated headless host passed the paired
non-Windows lifecycle after guarded wake31605729090; guarded sleep31606600532restored
the low-cost state. The physical Windows/WSL matrix separately passed 449 tests on real NTFS
and Ubuntu 24.04 filesystems. On 2026-08-12, the owner accepted another WSL Cloud lifecycle as
duplicate evidence rather than a launch gate. - Give the live staging E2E an owner-private persisted test profile or a short-lived
non-interactive test credential so reruns do not open PKCE login tabs or copy a user's
primary Orca session. - Verify staging application logs contain only route templates, method, status, duration, and
standard process metadata, with zero credential or private-content matches. Exclude per-link
Cloud Run platform request logs through Terraform and verify zero are retained. - Verify logs, metrics, traces, diagnostics, and support bundles contain no grants or private
package data. The physical SSH window emitted 27 structured route-template events whose
complete application field inventory was only duration, event, hostname, level, method,
process ID, route template, status, and time; it emitted zero API error logs. Install traces
retain only bounded IDs, counts, labels, and enums. The support-bundle suite injects private
paths, filenames, contents, share URLs, signed grants, policies, ACLs, and credentials and
proves none survive collection. No skill-sharing product telemetry event is registered. - Load-test finalization and choose the fixed semaphore from observed memory, CPU, request
latency, and database usage. Run31585710645kept the existing semaphore: its 12 concurrent
30-skill bundles produced two immediate successes and ten explicit saturation responses,
1,514 ms client p95, 8.95% API CPU, 26.95% API memory, one API instance, 10.89% database CPU,
25.59% database memory, and seven database connections. - Exercise at least one quarantine lifecycle deletion and published-object soft-delete
recovery. Published-object recovery passed run31580071168; the one-day quarantine
lifecycle deletion remains time-gated.
Production infrastructure and launch
- Apply the approved production Terraform plan and verify no unrelated replacement. The saved
skill-only plan added 40 resources with zero updates, replacements, or deletions; the
storage-monitor follow-up added one narrow IAM binding with zero changes or deletions. The
final hardening used two reviewed targeted phases: 13 additions plus one WIF update, followed
by exactly five broad-IAM deletions. A separate targeted apply made nine in-place
observability updates. No full plan or unrelated Relay drift was applied. - Run production database migrations before routing skill traffic. Disabled-route bootstrap
run31648015091completed the skill schema migration before any skill route was enabled. - Deploy the API with all skill flags disabled. Bootstrap revision
orca-cloud-api-00016-yem
received 100% traffic and returned404for the disabled skill route while Artifact and Auth
health remained green. - After every first-release gate passes, enable the feature for all accounts in one launch.
Production run31650178315initially enabled upload, download, remote install, and sharing
together. Hardening run31661421728then passed candidate and canonical authenticated
artifact plus skill smoke and promotedorca-cloud-api-00025-qupat 100% traffic with digestsha256:e4f044105ff2345574bdceb36eec1629761428cc2edf9d743939390b857d61e0. - Verify one complete share, local install, remote install, update, rollback, revoke, local
removal, Cloud deletion, upload expiry, and soft-delete recovery journey. Production CI has
verified the server-side share/version/grant/revoke/delete lifecycle; the signed-in desktop
and real-host production journey remains manual. - Verify upload, download, and remote-install kill switches independently.
- Review error budgets, cost, authorization denials, saturation, orphan counts, and support
signals. The hardened production revision has zero post-deploy error logs. All eight skill
alerts route to the enabled dedicatedOrca skill sharing alertschannel, and aggregate
monitor executionorca-cloud-skill-storage-monitor-lfd26passed on the serving API digest
with zero overdue objects. Sustained-usage review remains post-launch.
Launch operations
- Keep upload grants, download grants, and remote installation independently controllable.
Separate Terraform-owned Cloud Run variables gate upload/finalize/share publication,
download grants, and remote-target grants. Route tests prove each narrow control while local
grants remain available when only remote installation is disabled; staging advertises all
three explicit controls. - Disable the affected operation on unexplained digest mismatch, archive containment failure, data leak,
unrecoverable local mutation, or cross-tenant authorization defect. - Publish user documentation for sharing, access, install destinations, updates, rollback,
removal, retention, and trust indocs/reference/sharing-agent-skills.md. - Publish admin documentation for organization access, user departure, deletion, and retention
indocs/reference/admin-agent-skill-sharing.md. - Remove rollout flags only after sustained healthy usage and a reviewed decision.
First-release gate
- Threat model and privacy review are approved.
- Cross-platform package and transaction suites are required and green. Release publication
requires focused macOS, native-Windows, and Ubuntu 20.04/glibc 2.31 jobs with bounded archived
results; the latest recorded release and platform passes are green. -
windows 2native Windows and WSL release gates pass on real filesystems. The combined run
passed 449 tests across 67 files, and the owner accepted the additional live WSL Cloud
journey as duplicate evidence on 2026-08-12. Physical WSL disconnect/cancellation injection
remains open. - Real SSH paths and host-owned resolution pass. Native macOS ARM64 and Ubuntu 20.04/glibc
2.31 independently passed SSH-owned global install, update, rollback, managed inventory,
revocation preservation, removal, and Cloud cleanup without local fallback. - Mixed-version tests pass in both directions. Bundle/install capabilities are additive, the
cross-version wire suite passes client-newer and host-newer, and capability-loss tests prove
execution is blocked after a stale preview. - Cancellation and crash recovery pass during transfer and every commit boundary.
- Durable-share authorization, revocation lag, retention, deletion, and recovery are documented
and tested. The user/admin guides document the lifecycle; Cloud route, integration, restore,
and cleanup suites cover the implemented contract. Automatic one-day quarantine deletion
remains a separate time-gated infrastructure observation. - The UI identifies scripts and executable content before install without exposing internal
publisher or organization identifiers. Renderer coverage asserts script and executable
summaries, digest, release notes, and long-content behavior before the primary install
action. - Telemetry, logs, and diagnostic bundles contain no credentials or private contents. Cloud
field-inventory and sensitive-value scans, bounded install-span attributes, and adversarial
support-bundle collection tests cover the complete first-release data path. - Kill switches are tested without affecting discovery or existing installations. Cloud PR
#342proves unlisted-link preview remains available with upload, download, and remote
installation controls disabled; existing route coverage proves remote-off retains local
grants. Existing installations and local discovery use only host-owned files and receipts.
15. Post-release multi-machine management and reconciliation
- Measure sharing, installation, update, conflict, fallback, failure, and multi-machine demand
without collecting private contents. - Add Install on another machine and bounded multi-machine progress.
- Add optional desired-version policy for selected personal or organization machines.
- Add opt-in drift and missing-install reconciliation that never overwrites modifications.
- Evaluate an explicit, reviewable project desired-state manifest.
- Evaluate direct machine-to-machine transfer if Cloud persistence is not appropriate.
- Define offline convergence without durable shared credentials.
- Document organization removal, user departure, package retention, and reconciliation
semantics before enabling policy-driven installs. The admin guide explicitly leaves legal
retention and ownership-transfer approval as human release gates.
16. Agent and Orca CLI publishing
- Add a separate
agentSkillSharingEnabledcapability that defaults off and accepts only an
exact persistedtruevalue. Manual desktop publishing remains independent. - Let only the local desktop Settings → Share Skills switch grant the capability. Runtime
settings.getexposes a fail-closed read-only projection;settings.update, paired web,
mobile, agents, and the CLI cannot enable it. - Add
orca skills installed --jsonwith discovery IDs and names but no local filesystem paths. - Add
orca skills sharewith repeated explicit--skillselectors, a required bundle name,
one unlisted link for multi-skill bundles, and no--allor arbitrary-path input. - Resolve exact discovery IDs before unambiguous names; fail missing, ambiguous, and du