* fix(worktree): remove a clean checkout that contains submodules
Git refuses to remove any worktree with an initialized submodule unless --force is set, even when nothing is uncommitted. After that specific refusal, re-check the checkout including files inside the submodule and pass --force once. A dirty submodule still blocks the delete. A second --force is not added, because that would also clear a Git lock.
* fix(worktree): offer Force Delete for a checkout Git refuses over submodules
Drop the automatic --force retry. A forced removal deletes the submodule's
repository under .git/worktrees/<id>/modules, including commits, local
branches and stashes that were never pushed, and the parent's dirty check
cannot see them (the retry #24602 removed for the same reason).
Instead classify Git's "working trees containing submodules cannot be moved
or removed" refusal as a new Force Delete reason, 'submodules', after the
lock check and the force guard. The renderer classifies the error string,
so older SSH and paired hosts get it too with no wire change. The toast,
the delete dialog and the Space manager row show one plain warning naming
what Force Delete would lose instead of Git's text.
Keep --ignore-submodules=none in the pre-teardown dirty check so a user's
ignore setting cannot hide submodule edits from it.
Replace the retry test with one real-Git test: a clean checkout whose
submodule holds a local-only commit is refused without --force, stays
registered, keeps the commit, and its refusal classifies as 'submodules'.
* fix(worktree): clarify the submodule Force Delete warning and hide View for it
The warning now says Git deletes the workspace only with Force Delete, the submodule
refusal no longer offers View (there are no changes to show), the real-Git test pins
that Force Delete then succeeds, and a comment no longer describes the removed retry.
* fix(worktree): name submodules when a changed submodule blocks the delete, and clear a stale delete toast
With a commit inside a submodule that the parent has not recorded, the parent sees `M vendor`, so
Orca's dirty check refused first with the generic "It has changed files". Force Delete then destroyed
the never-pushed submodule commit without warning. On a dirty refusal the check now asks
`git status --porcelain=v2` whether a changed entry is a submodule, and if so reports a refusal the
app shows with the submodule Force Delete warning. The detail keeps the dirty sentence, so older
apps still offer Force Delete.
After the Space manager deleted a workspace (its row Force, or a later delete), the failure toast for
that workspace stayed up and still offered Force Delete. A Space delete now dismisses it.
* fix(worktree): warn about submodules on any refused delete of a checkout that has them
Review found the warning still vanished when a submodule commit was already recorded and some other
file was dirty: the parent's status shows no submodule change, yet Force Delete destroys the commit.
The changed-files check now asks whether the checkout has a populated submodule (a .gitmodules path
holding a .git, as Git's own refusal checks) instead of whether one changed. The refusal keeps the
changed paths after the detail, and View stays available since there are changes to view.
* test(worktree): mock the .gitmodules probe as missing in the preflight unit tests
* refactor(worktree): attach the dirty-check status without a type assertion
* fix(worktree): key the delete failure toast by host and worktree id
A Space delete on one host must not dismiss another host's failure toast for a worktree with the same id.
* fix(worktree): dismiss the failure toast by the store row's host after a Space row Force
* Persist session parse results beyond the memory cache limit
* Expire Session History parse rows by idle time instead of a count cap
The parse cache dropped its oldest row once it held 4,096, so a scan over
more transcripts evicted its own upcoming hits and re-parsed every file on
every scan. Rows now stay until no reader has used them for 24 hours, swept
after each completed History scan and each search-index pass. Only the live
parser state keeps a 4,096 count bound; dropping it costs one whole re-read
of a file that changed, never a re-parse of one that did not.
Removes the overflow directory and the orcad persistence wiring from the
earlier revision of this change.
* Expire only rows History lists, and keep the newest transcripts under bounds
The search pass ran the idle sweep every 20 s but never re-stores History's
unchanged rows, so with search on a day away from History dropped them all
and reopening it re-parsed everything. Only a completed History scan sweeps
now, and it expires only rows History listed (on a monotonic clock). Rows
only the search index or a title lookup created keep main's 4,096 count
bound, so an index build can't grow the scanner process without limit.
Scans store newest transcripts first, so every store-order bound dropped the
newest, most active files: the saved snapshot kept the oldest ~11k rows and
the resume bound stripped the newest files' parser state. The snapshot is now
ordered by transcript mtime, and both count bounds drop the tenth of their
rows with the oldest transcripts.
* Drop the 24 h idle sweep; History rows live for the process
The sweep let whichever listing ran last decide when a row died, and
listings differ in scope: a smaller one (the phone's Agent History, a lower
desktop limit) expired rows only a larger one used, so the next Unlimited
scan re-parsed them. Rows History lists now live as long as the scanner
process; the snapshot's newest-transcripts cap bounds what survives a
reload, and rows other readers made keep the 4,096 bound.
Also clears the resume bookkeeping for a row the unlisted bound drops in the
same store.
* Skip resume bookkeeping for a row the unlisted bound just dropped
Running the resume trim first let a row the unlisted trim then deleted push
the resume set over its bound and cost 409 live resume points.
* fix(orchestration): steer mail notices into a busy native chat's running turn
A native-chat coordinator's mail notice always queued as a card until the whole turn ended, so a
coordinator busy in a long command never saw its workers' reports. Agents that take a message
mid-turn (Claude, Codex, Pi) now get the notice at once; agents that would cancel their turn for
it (ACP) still queue it.
* fix(orchestration): drain mail that arrives while a notice is pending
* Send orchestration mail the same way to every chat agent
A mail notice is a plain send for every agent, as a person's own mid-turn message is.
While a pending approval or question waits on the person it is held, and the turn's
working edge after the answer sends it.
* Hold orchestration mail at live prompt admission
Since #29209 a full orcad on Windows runs wsl.exe in its profile directory.
On windows-11-arm the WSL service keeps that empty directory busy for ~60s
after every process in the test's tree has exited, past removeTestRoot's
~45s retry budget, so the recipe and D7 cases fail cleanup with EBUSY.
The warm-match (220 ms) and search+sort (180 ms) ceilings failed on shared
CI runners at 211-245 ms for unchanged code. Compare one full-corpus pass to
four quarter-corpus passes timed in the same run: ~1 for linear work, ~4 for
quadratic, independent of runner speed.
* perf(dev): keep pairing web bundle after test file changes
* fix(dev): detect web input replacement within one timestamp tick
* test(dev): use real files for web replacement coverage
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* perf: reduce sidebar agent status subscription work
* test: align sidebar store mocks with subscription API
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
optionalAskTimeout computed exactly what cappedWait does (an undefined timeout stays undefined;
anything else clamps to 0..ORCHESTRATION_ASK_MAX_TIMEOUT_MS), so the ask call uses cappedWait.
Three functions used only inside their own file lose their export, and the per-host switch's
doc names the orchestration commands it has gated since #29571.
Co-authored-by: Claude <noreply@anthropic.com>
* Run automated workspace setup in a dedicated tab
* Preserve native-chat setup route coverage after placement retirement
* Preserve Setup tab design and independent review evidence
* Classify activation test fixtures consistently for routing checks
* Sync runtime English catalog after removing setup preferences
* Update listener savings model after retiring setup action
* fix(terminal): open the picked Windows shell on a managed host instead of typing it into PowerShell
* fix(terminal): keep the shell pick through materialization and folder runtimes
* fix(terminal): refuse a shell pick on agent launches and update paired-tab expectations
* test(terminal): type the shell-selection runtime fixture instead of casting
resolveResumeLaunchRecipe now also carries the recorded agentCommand and ompResumeFilePath, so
the three resume lanes stop repeating the same conditional spreads. The rule is unchanged: a
resume relaunches with exactly what its launch recorded, or nothing.
Co-authored-by: Claude <noreply@anthropic.com>
The synchronous drainAgentHookSpool and currentAgentSessionThreadGoalBySequence lost their
last production callers earlier today; tests now exercise the functions production calls.
git-object-quarantine reuses isDefinitiveAbsence instead of a private ENOENT/ENOTDIR copy.
Co-authored-by: Claude <noreply@anthropic.com>
The deploy's pinned-runtime download (SSH, WSL host, Windows host) passes no fetcher, so it goes
through getMainHttpClient(), which the desktop backs with Chromium's default session: system, PAC
and Orca proxy settings and proxy login apply. Guard that a plain global fetch never replaces it.
Co-authored-by: Claude <noreply@anthropic.com>
A Claude chat's / menu listed /color, /doctor and /reload-plugins until the
first message ran, then dropped them: the CLI names its terminal-only commands
only in the init frame a turn emits, and the catalog treated that list as a
hide-list. Chat runs all three (/color and /reload-plugins answer locally,
/doctor is an ordinary turn), so the catalog now keeps every command Claude
reports and the menu no longer changes on the first message.
- Specs with their own CI jobs were also being assigned to general E2E shards, so they could run twice or in the wrong job.
- planE2e now drops dedicated specs before balancing and fails if no general specs remain, instead of producing an empty plan.
- The mobile subscribe spec builds a paired runtime connection from the pairing offer before sending requests, matching the updated remote runtime helper signatures.
* fix(settings): show a Keychain unlock on its own instead of leaving the locked notice up
The server already recovers on its next status read, but Settings read Linear
and Jira status once, so the locked notice stayed until the user pressed Test.
While the notice is up it now re-reads its provider's status every 10 seconds.
* fix(settings): wait for each Keychain status read before the next
A newer status read discards an older one, so an interval could keep
discarding every slow read and never show the recovery.
* fix(settings): poll the Keychain status with an in-flight guard
* fix(wsl-cli): keep the WSL CLI status check from timing out on a cold or slow-profile distro (#29497)
* fix(wsl-cli): run install/remove without the login PATH; a PATH-check timeout reports PATH unknown
* Finish structured agent readiness before delivering held work
* Adapt permission fixtures to startup lifecycle
* Keep model discovery for chats before their first start
* Use one host readiness promise for provider operations
* Await provider opening in the execution host
* Await Claude initialization in host-owned open
* Observe Claude readiness through the host status
* Keep optional readback behind host readiness
* Recheck launch permission after host readiness
* Preserve the awaited child during permission revalidation
* Record failed owned opening before the first message
* Abort a matching provider open when its child exits
* Cover provider exit during an unanswered host open
* Cover explicit failure facts on a host-stopped child
* Keep startup waits cancellable and preserve operation order
* Keep live choices and delivery order while agents start
* Apply startup picks and let parked messages release operations [skip ci]
* Save a waiting startup pick after Stop ends its opening [skip ci]
* Save choices when startup ends or a replacement is still opening
* Check saved choices separately from Claude startup reports
* Align startup fixtures and preserve observed Claude exits
* Order startup picks before later messages and retain flow regressions
* Keep reviewed lifecycle contracts while pruning duplicate tests
* Save picks after a proven exit without requiring a child
* Type the startup reply fixture against the Claude control contract
* Honor the before-input callback in the startup reply fixture
* Keep startup option menus independent and show the saved choice
* Exercise startup picks across real menus and the runtime
* Keep saved effort visible when picking a model during startup
* Include the running-turn scope in repeated Stop fixture
* Use the current attach contract in import fixtures
* Align carried-message fixture with startup event contract
* Keep launch-only permission picks usable while prior writes wait
* ci: rerun on main with the locale fix
* Preserve goal retries and chat launch selections
* Give launch regression its request identity
* test(native-chat): type the provisional-launch hook's session prop
* fix(projects): refuse a folder-to-Git type change that would hide folder workspaces (STA-10638)
A folder project's extra workspaces exist only as metadata rows that the folder listing shows. Changing the project to Git switches it to `git worktree list`, so those workspaces drop out of the next refresh and their saved tabs are purged as if they had been deleted. Only the automatic upgrade checked for this; `orca project setup-update --kind git`, repo updates and clone-into-folder did not.
The check now lives in the one repo write every type change goes through, so any caller gets the same refusal and nothing is written. The automatic upgrade and both clone paths use the same check and keep the project as a folder project.
* test(repos): give the repo handler store mock the metadata read the clone path now uses
* test: run repo catalog request coordination in Bun
* fix: remove redundant chat callback alias to restore lint
---------
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* fix(relay): destroy a pooled client whose session the server ended, and mark work that never ran
pg keeps calling a client queryable until its socket's end arrives, so a client released after
57P01 (pg_terminate_backend, shutdown) or a dropped socket went back to the pool and failed the
next caller's statement with 'Connection terminated unexpectedly'. Such clients are now released
with the error, so pg-pool discards them.
Also marks failures where no statement of the operation reached the server (acquire, a failed
BEGIN, a client dead at checkout) so a caller can tell work safe to re-run.
* fix(relay): skip ROLLBACK on an ended session, drop unused nothing-ran marking
Adds 57P04/57P05 to the session-ended codes and covers 08xxx, a dropped
socket, an ordinary 23505, and a real pg_terminate_backend.
Claude-Session: f2fe8cb5
* Ask Claude for a resting chat's / menu instead of rebuilding it from folders
A Claude chat whose Claude isn't running (a new chat, or any chat after a
restart) offered only clear/compact plus folder commands and skills, so
Claude's own built-ins like /goal, /init and /review were missing until the
first message started Claude.
The at-rest catalog now sends Claude one initialize control request in the
chat's launch folder, through the same sessionless path the model list uses
(same binary, env, account pin and supervised runner), and shows the list
Claude answers. Hooks and MCP servers are off for that one process. Results
are cached per workspace and account for 5 minutes, and at most two probes
run at once. If Claude can't answer, the folder scan is used as before.
* fix(claude): keep Claude's own / list when a later refresh fails
* fix(claude): preserve CLI commands across overlapping fallback scans
* fix(claude): keep overlapping command refreshes eligible
* test(claude): await fallback settlement in overlap case
* test(pty): pin where the two spawn lanes build a daemon spawn's host env differently
Characterization on main's code before the host-env step merges: which failed env builds sweep
per-PTY state (minted ids only, both lanes), the empty-id split, the host-only WSL Pi/OMP guest
readiness wait, and that a host create operation still waits on a managed Codex login.
* refactor(pty): one host-env step for both terminal spawn lanes
The window (pty:spawn) and host (runtime controller) lanes each carried a copy of Codex resume,
Codex home selection, the managed-auth wait, backfill recovery and the daemon host-env build.
Both now call resolvePtySpawnHostEnv. Lane differences are explicit inputs: two TEMPORARY ones
(the host-only WSL Pi/OMP guest readiness wait; the host skipping host env for an empty id) and
three that mean the same in both lanes. The resume step's launchEnv was never read and is gone.
* test(pty): pin that both lanes hand Codex resume prep the request's CODEX_HOME
orca serve (#29209) skips resume prep for a launch whose env names a client-sent CODEX_HOME.
* test(pty): type the new lane-pin requests instead of asserting them
* test: pin selected Codex home on terminal restart
* test(launch): pin what a paired agent session spawns today
Characterization on main's code before createAgentSession moves onto the
host planner: plain launch, short/long/multi-line prompts on macOS, a
stdin-prompt agent starting clean, Windows PowerShell and cmd quoting, and
the identity-required refusal when no launch can be built.
* refactor(runtime): plan paired agent sessions with the host planner
terminal.createAgentSession built its own launch command beside the planner
every other host launch uses, and the two had drifted. It now hands the
request to createTerminal as a startupAgent, so one planner builds it.
The planner gains a draft delivery input, and one TEMPORARY input
(startupPromptCarry: 'force-line') keeps what createAgentSession did: the
prompt always rides the typed line, and an agent that reads its prompt
after start launches clean. The later change that delivers uncarried
prompts after start removes it. A launch that can't be built still refuses
with agent_session_identity_required.
* test(accounts): pin that the selected account wins over Settings agent env
A paired agent session now layers the host-selected account env the way
every other host launch does.
* fix(runtime): report prompt carry truthfully on the planner's direct path
Review round 1: a stdin-prompt agent's text never rides its launch line, a
draft comment that overstated the carry rule, the error class's name, and
formatting the cherry-picked commit skipped.
* docs(runtime): scope the account-order and draft comments accurately
Review round 2.
* test(launch): drop paired-session parity rows that pin nothing new
The no-prompt and short-prompt rows plan the same with or without
force-line, and the Windows cmd row repeats the OpenCode-model Windows
row's createAgentSession runtime and cmd quoting. The long-prompt,
aider, PowerShell, refusal and account-env pins keep the coverage.
* docs(runtime): label the draft's carry-rule exemption temporary
Skipping the typed-line carry rule for drafts is createAgentSession
parity, not design: a draft on the line has the same hazards as a
submitted prompt. The one prompt/draft delivery policy removes it with
force-line.
* fix(runtime): keep main's error name for an unbuildable agent launch
Overriding the name made every other planner caller's stringified
error read 'AgentLaunchUnbuildableError: ...' instead of main's
'Error: ...', which renderer code that strips an 'Error:' prefix
would show raw. createAgentSession matches by instanceof, so the
override bought nothing.