* fix(win): taskkill plain-shell PTY trees on immediate teardown
Deleting a Windows worktree that still has a live process in a terminal
tab (a `pnpm i`, or any command that spawns a child tree) failed with
"Failed to physically stop every PTY for worktree" after the full 10s
teardown deadline.
Root cause: on Windows, closing a plain shell's ConPTY does not reap its
orphaned children — node-pty's `useConptyDll` skips the console-process
reap. A live `pnpm i`/`node` child survives the shell's exit, keeps the
ConPTY console non-empty (so the daemon still reports the session alive),
and holds the worktree cwd handle. The destructive-removal physical-stop
check then fails closed. #10100 fixed this for agent sessions
(killWithDescendantSweep -> taskkill /T /F) but plain shells were never
swept.
Fix: extend the Windows taskkill /T /F descendant tree-kill to non-agent
shells on the immediate/destructive teardown path, in both the daemon
(TerminalSessionTeardown) and local provider. Gated to win32 + immediate
with the same ownsRoot guard so a naturally-exited/recycled PID is never
signalled; POSIX shells keep reaching their child pgroup via forceKill
and are unchanged.
Reproduced and verified end to end via the Electron dev build: a worktree
with a live node child previously failed to delete after 10s; with the
fix the child tree is taskkilled, the directory is removed, and the
delete succeeds in ~1.8s.
* fix(win): claim plain-shell termination before the taskkill sweep
forceKillAndWaitForExit sets _isTerminating in its synchronous prologue.
Awaiting the Windows descendant sweep ahead of it left createOrAttach's
doomed-session guard open for the taskkill's duration, so a concurrent
attach could bind a pane to a session about to be tree-killed.
* fix(skills): source released history from the committed ledger, not a tag walk
verify:skill-bundle-manifest rebuilt the entire released-skill history by
walking every local refs/tags/v* on each run and demanded byte-equality with
the committed artifacts. Output was therefore a function of (skill bytes x
local tag set x release timing), so any clone holding stray, deleted, or fork
tags the committed artifacts predate rebuilt a divergent registry and failed
lint. This was the 4th instance of one failure class (#8637 -> #9119 version
bumps -> #9778 new tags -> local tag drift), each patched with a new tolerance
rather than removing the tag coupling.
Fix: the committed snapshot-registry + release-mapping ARE the released history;
trust them instead of re-deriving from tags.
- releasedHistoryFromCommitted() seeds generation from the committed ledger,
dropping the floating unreleased tail (entries beyond what the mapping names).
verify and --write are now pure functions of working-tree bytes with zero tag
access. The tag walk survives only behind --rebuild-from-tags (disaster
recovery), off the everyday path.
- --release <version> + appendReleaseRow() perform the O(1) append of one
mapping row at release cut (dedupes vs the last row, strips the v-prefix) --
the single authoritative point where working-tree bytes become an immutable
released revision.
- release-cut.yml runs generate --release "$VERSION" before the release commit
(Node built-ins only, no install needed); pr.yml drops fetch-depth: 0 from the
lint job since verify no longer needs tag history.
Recognition is unaffected: the runtime uses knownSnapshots = registry.skills
(all entries, incl. the tail committed at PR-merge time), so a missing mapping
row only loses a version label, never recognition or the update nudge.
Trade-off: lint no longer cross-checks committed historical snapshots against
tags. A hand-edit to an old released entry is still caught by the runtime
manifest<->registry consistency check when the current manifest points at it,
and can be audited anytime with --rebuild-from-tags.
Verified: verify passes committed-sourced; --write is zero-diff (byte parity);
a planted stray v-tag no longer changes output; edit-stub -> --write -> --release
appends the correct single row; double --release is idempotent;
--rebuild-from-tags reproduces the committed artifacts. Generator tests 14 pass/
1 skip; runtime skill-bundle-artifacts + freshness-inventory 14 pass; bundled
skill guides verify passes.
* fix(skills): keep one release-mapping row per version on a re-cut
A cut that pushed the version bump to main but died before pushing the
tag is re-cut at the same version. If skills changed in between, the
second --release appended a duplicate row, and the stale one named
revisions that tag never ships — which verify-skill-update-roundtrip
then pairs with the tag's real bytes.
Overwrite the trailing row instead (the tag is absent, so that version
was never published). Refuse only when an earlier row claims the
version, which the cut workflow already rejects upstream, so this
cannot wedge a recovering cut.
#9343 broke two contracts at once and #10437 fixed both, but neither is
asserted: #10429 repaired the failing tests by deleting the poll assertion and
by handing the retirement store a live repo, so a revert would land silently.
Restore `expect(getRepos).not.toHaveBeenCalled()` on the floating-tab poll, and
add a hydrate case for a store that cannot report repos. Verified both fail
against the pre-#10437 code: the poll assertion reports getRepos "called 2
times", and the hydrate case returns [] instead of the persisted tab.
* fix(codex): preserve runtime config without system source
* fix(codex): retain baseline when mirror is skipped
* refactor(codex): extract deprecated hook-flag normalization
Why: codex-config-mirror.ts sat at the 300-line cap, so the missing-source
guard could not land without a max-lines disable.
* fix(codex): bootstrap a baseline when the mirror is skipped
Why: a runtime home seeded outside the mirror (WSL, per-account) never got a
baseline while the source was missing, so promotion stayed inert and silently
reverted the in-Codex change once the source returned.
* fix(codex): stop a synthesized source config from wiping runtime settings
Two routes still reached the #9073 data loss after the missing-source guard:
- Promotion runs before the guard and, with no ~/.codex/config.toml, created
one holding only the promoted keys. The next mirror treated that skeleton as
authoritative and deleted every other runtime setting. It needs no missing
file: `codex mcp add` inside an Orca-launched Codex plus /model was enough to
drop the MCP server for good. Promotion now seeds a brand-new system config
from the runtime's ordinary settings, so the mirror round-trips them.
- A 0-byte source (half-written, or an unhydrated cloud-synced home) still read
as an authoritative empty config and advanced the baseline, making the loss
unrecoverable. A blank source is now treated like a missing one.
Moves the TOML section model out of codex-config-mirror.ts so promotion can
share it without a cycle.
---------
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* fix(runtime): stop the headless hydration repo gate from dropping every tab
#9343 gated headless mobile-session hydration on the live repo list, but read
it as `this.store?.getRepos?.() ?? []`. A store that cannot report repos then
yields an empty set, which reads as "every repo is gone" and skips every
parseable session key — so no tabs hydrate at all. It also called getRepos on
every hydrate, including the hot floating-tab poll path that is contractually
free of repo/provider inventory work.
Resolve the inventory lazily and only for keys that parse to a repoId, and keep
`null` (unavailable) distinct from an empty list (all repos really gone). An
absent list now fails open; a known list still prunes as #9343 intended.
Fixes 3 tests that have been failing on main since #9343 landed:
orca-runtime-terminal-retirement (2) and orca-runtime (1).
* refactor(editor): extract the pending-focus effect to clear the max-lines cap
#8083 pushed RichMarkdownEditor.tsx to 404 counted lines against the 400-line
.tsx cap, failing lint for every PR that merges current main. The Explorer
find-focus request is self-contained, so it moves to its own hook.
* Fix fork PR/MR worktree creation race via durable review-head refs
When creating a fork PR/MR worktree, concurrent `git fetch origin` operations
clobber the shared FETCH_HEAD, causing the wrong commit to be checked out.
Fetch PR/MR heads into dedicated per-review refs (`refs/orca/pull/<N>`,
`refs/orca/merge-requests/<N>`) that persist and isolate each head from other
fetches. Gracefully keep the compare-base when the fetch fails but the local
ref already exists, avoiding silent fallback to the wrong branch on transient
network errors.
* Bound PR/MR head fetches with 60s timeout
Prevent PR/MR creation from hanging when a remote is stalled or
unreachable. Both GitHub and GitLab head fetches now enforce a
60-second timeout, matching the bound used in the create-path
fetch. Durable refs (refs/orca/pull/*, refs/orca/merge-requests/*)
decouple the ref from FETCH_HEAD, preserving legacy client semantics.
* test: align CI expectations with main PowerShell/sparse regressions
PR checks merge into main, which recently changed PowerShell launch args
(cwd restore after profiles) and sparse-checkout detection (require
core.sparseCheckout). Derive PowerShell spawn args from the production
resolver, mock the sparse config flag, reset shared worktree list scan
cache between tests, and stop requiring floating polls to avoid getRepos
hydration.
* Address review follow-ups on durable review-head refs
- Unify PR review-head remote selection: local and SSH GitHub paths share
resolveGitHubReviewHeadRemote, which prefers the remote mapping to the
hosting GitHub project (upstream before origin, matching work-item/API
candidate order) so contributor clones fetch refs/pull from the repo
that actually hosts the PR.
- Soft-keep durable review heads: when the PR/MR head fetch fails but
refs/orca/pull/<N> / refs/orca/merge-requests/<iid> still resolves,
keep the pinned SHA (warn) instead of failing resolve, mirroring the
compare-base fallback. Extracted shared compare-base soft-keep into
compare-base-ref-fetch.ts.
- Extract fetchGitLabMergeRequestHeadRef (local + SSH) parallel to the
GitHub helper; bound its local fetch with the shared 60s timeout.
- Share relay-style fetch validation (positive safe-integer id, remote
not starting with "-") between relay and local helpers via
review-head-tracking-ref.ts; move REVIEW_HEAD_FETCH_TIMEOUT_MS there.
- Drop the githubPullRequestHeadLocalRef re-export; resolve head SHAs via
rev-parse --verify <ref>^{commit}.
- Add GitLab anti-FETCH_HEAD regression test plus durable-head soft-keep
and remote-selection unit tests.
Co-authored-by: Orca <help@stably.ai>
* test: supply live getRepos for terminal-retirement hydrates
Main's headless tab hydrate (#9343) skips worktree keys whose repo is not
in getRepos. Retirement tests that rebuild mobile tabs from a persisted
session now advertise the fixture repo as live so PR Checks merge stays green.
* fix(editor): extract RichMarkdownEditor props to stay under max-lines
Main's SSH external-image wiring (#10323) pushed RichMarkdownEditor.tsx over
the 400-line tsx budget, failing PR Checks lint on every merge into main.
Move the props type into a sibling module so the component stays under the
limit without disabling max-lines.
* Make durable review-head refs remote-identity scoped
Embed remote name + URL hash into refs/orca/pull|merge-requests refs to prevent soft-keep from serving wrong project's PR/MR when FETCH_HEAD is clobbered by concurrent fetch. Fetch functions now return the written ref path (writer-authoritative) so callers rev-parse exactly what was fetched, not re-derive identity. Soft-keep only applies to transient errors (timeout, network); fails hard on missing refs, auth failures, and stale relay. Relay returns localRef so client avoids re-hashing (URL normalization can disagree).
---------
Co-authored-by: Orca <help@stably.ai>
* feat(agent-dashboard): choose in-window screen popover or pop-out window
The experimental Agent Dashboard opened only as a separate pop-out
window. Add an "Open as" mode under the experimental toggle so it can
open as an in-window screen popover (new default) or a pop-out window
(prior behavior). The mode row appears only when the feature is on.
- New setting `experimentalAgentDashboardMode: 'in-window' | 'popout'`
(default in-window); sidebar entry branches on it.
- In-window: AgentDashboardOverlay renders the shared AgentKanbanBoard
in a near-fullscreen dialog, snapshot built locally via
useLiveDashboardSnapshot (the pop-out relays over IPC; in-window has
no relay). Ack/reveal act on the local store — the pop-out IPC
handlers are gated to the pop-out renderer.
- AgentKanbanBoard gains containerClassName/onAckAgent/onRevealAgent/
onClose props; defaults preserve the pop-out behavior.
- Extracted AgentDashboardExperimentalSetting to keep ExperimentalPane
under the max-lines cap.
* fix(agent-dashboard): admit main renderer to terminal-preview IPC for in-window dialog
The terminalPreview:* handlers gated every channel to the pop-out
renderer, so the in-window overlay's terminal dialog (running in the
main renderer) got { snapshot: null } from connect and falsely showed
"No live terminal — this agent's pane has closed." for live agents.
Accept the trusted UI renderer too — it already has full PTY access
through the regular terminal channels, so this adds no reach.
* fix(agent-dashboard): sync locale catalogs for new mode/close keys
verify:localization-catalog (part of lint CI) fails when en.json keys are
missing from the other locale catalogs; run sync:localization-catalog so
the six new agent-dashboard keys exist everywhere (English fallback text;
translated copy remains the documented follow-up).
* feat(agent-dashboard): present in-window mode as a companion board sheet
The in-window dashboard now uses the same non-modal left sheet as the
workspace kanban board — anchored to the sidebar edge, chrome/status-bar
bounds, sidebar stays interactive — instead of a near-fullscreen modal
dialog. Both companion boards are mutually exclusive; the sidebar entry
toggles the drawer. Removes the modal focus-restore timing coupling on
reveal.
* fix(agent-dashboard): ignore Radix dismiss requests like the workspace board
Non-modal Radix layers also request dismissal for interactions the drawer's
outside guards cannot classify — focus moving outside carries no pointer
coordinates, and clicks in the status bar / top chrome fall outside the
right-side dismiss band. Forward only open requests from the Sheet, matching
WorkspaceKanbanDrawer, so only the drawer's own escape/outside/close paths
close it.
* fix(agent-dashboard): guard reveal relay and refresh stale mode copy
CodeRabbit review: revealAgent lacked the ?. HMR-skew guard its sibling
ackAgent has (both channels shipped together, so a stale dev preload
lacks both). The es/ja/ko/zh catalogs also still described the dashboard
as pop-out-only in stale English, contradicting the new in-window
default; refreshed to the current English source.
* feat(agent-dashboard): add board settings menu to the in-window header
Mirrors the workspace board's settings gear: an Open as segmented control
in the board header so the mode is changeable without opening Settings.
Switching to pop-out hands the surface over (closes the drawer, opens the
window) instead of leaving a board the setting says should be a window.
In-window only via an optional headerActions slot - the pop-out renderer
has no store to drive it.
* fix(agent-dashboard): reset the settings-menu flag when the drawer closes
The pop-out hand-off closes the sheet while the menu is still open, so the
menu unmounts without Radix reporting onOpenChange(false). The stale
menuOpen=true then blocked outside-dismiss permanently on the next
in-window open. Mirror closeWorkspaceBoard by resetting the flag in close.
* fix(agent-dashboard): reset the menu flag on store-driven drawer closes
Cmd+B sidebar collapse and the workspace-board exclusivity effect close
the drawer via setAgentDashboardDrawerOpen directly, bypassing close();
a settings menu open at that moment unmounted without Radix reporting
onOpenChange(false), leaving menuOpen stuck true and outside-dismiss
disabled on the next open. Sync the flag to the open state so every
close path resets it.
Stacked layout (head / → base) lets long branch names fit narrow sidebars
without truncating either line. Also show head-only identity when compare base
isn't configured, and improve upstream vs compare target distinction in stats.
Move link-tooltip chrome into terminal.css so offsets cannot drift via
inline styles, and square the bottom-left corner so the hover preview
sits flush against the pane edge (Ghostty-style).
An image send whose text+Enter RPC ended 'unknown' (ack loss / path
cutover) collapsed to accepted=true, so the terminal was never marked
stale. When the Enter truly never landed, the already-pasted image path
sat on the input line and glued onto the next plain-text message.
Propagate the send outcome through handleNativeChatSendWithOutcome and
mark the terminal input stale on any non-accepted outcome; the next send
heals with Ctrl+U (a no-op when the message did land). Chips still clear
on 'unknown' to avoid a double-send on retry.
* fix(terminal): reveal Markdown links at target lines
* fix(terminal): scope line reveals to opened tabs
---------
Co-authored-by: kaynan <kaynan.camargo@terceiro-sky.com.br>
* Normalize branch prefixes and flag invalid ones in settings
A custom branch prefix ending in a slash (e.g. "team/") produced a
double-slashed branch name like "team//feature" that git rejects, and
the raw check-ref-format error gave no hint that the prefix caused it.
- Normalize the configured prefix (trim whitespace, strip leading/
trailing and duplicate slashes) in the shared branch-name builder so
the common trailing-slash case just works, for local and SSH worktrees.
- Validate the prefix on the worktree-create path (computeValidatedBranchName)
so a genuinely invalid prefix fails fast with a clear
"update it in Settings -> Git" message instead of an opaque git error.
- Add a live BranchPrefixFeedback under the Branch Prefix setting: previews
the resulting branch name, warns on invalid characters, and notes when a
prefix collapses to none.
- Keep the background first-work rename on the non-throwing builder since the
prefix is already validated at create time.
* Keep caret in place when editing the branch prefix
The custom branch prefix input was directly controlled by settings, but
updateSettings persists through an async IPC round-trip, so the value
updated a tick late and React re-assigned it, snapping the caret to the
end on mid-string edits. Drive the input from a local draft and only
adopt genuine external settings changes so the caret stays put (and fast
typing survives slow SSH round-trips).
Co-authored-by: Cursor <cursoragent@cursor.com>
* Return ReactNode from BranchPrefixFeedback
JSX.Element needlessly excludes null/string/number returns; ReactNode
keeps the component's return type from over-constraining future changes.
Co-authored-by: Cursor <cursoragent@cursor.com>
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
* test(session): guard against workspace-reopen tab fork-bomb (STA-1111)
Relates to STA-1111 (already fixed on main by #6945).
The runaway-tab-on-reopen bug was fixed by #6945 (activeOrQueuedResumeClaimsProviderSession dedup guard) hours before the ticket was filed. Verified: disabling that guard makes tab count climb 1->2->3->4 across reopens; restoring it holds at 1. This adds a revert-sensitive regression test covering the created-agent and sleeping-resume reopen paths so it cannot regress.
Test plan: 2 new tests pass; fail if the #6945 guard is removed.
* test(session): isolate automatic resume replay coverage
* test(session): assert resumed tab identity stays stable
* fix(window): reflow on macOS occlusion-reveal so the bottom bar is not clipped
Fixes STA-2383.
On macOS the window is background-throttled while hidden; on occlusion-uncover only 'focus' fires and its handler runs webContents.invalidate() (the setSize jiggle is skipped to avoid SIGWINCH-ing terminals). invalidate() repaints but does not reflow, so the app-shell h-dvh root keeps a stale dynamic-viewport height and the StatusBar is clipped below the viewport ('no bottom bar'); a manual resize restores it.
Fix: renderer relays a genuine hidden->visible reveal (visibilitychange) to main via new ui.notifyWindowRevealed IPC; main runs the same proven forceRepaint. Occlusion-gated (no per-Cmd+Tab SIGWINCH regression), darwin-scoped, sender-guarded, cleaned up on close.
Test plan: vitest createMainWindow + web-preload-api green. Recommend on-device macOS occlusion QA before merge.
* fix(window): preserve user resizes during reveal repaint
createPtySubprocess asserted assertSafeAgentStartupCwd on the raw
opts.cwd before applying opts.cwd || getDefaultCwd(). An omitted cwd
was treated as root-like and threw even though the post-fallback home
default is safe — while LocalPtyProvider already gates the effective
cwd after fallback.
Compute requestedCwd first, then gate. Update the daemon unit test to
assert omitted-cwd agent launch uses the safe default (#9578).
The New Workspace flow built a bare launch command client-side and sent it as
`startupCommand`, so the host ran it verbatim and never applied the default
launch args. The first Claude session therefore started in manual mode, while
opening another Claude via the "+" tab (which sends the agent id and lets the
host resolve args) started with `--dangerously-skip-permissions`.
Send `startupAgent` from every client-built create path (blank, reuse-branch,
and new-branch) so the host resolves the launch command, args, env, and
host-shell quoting through the same path the "+" new-tab and CLI use. The
work-item path already delegated via `startupDraft`. Custom `agentDefaultArgs`
are now honored on all paths.
Adds a shared `agentLaunchCreateFields` helper and removes the now-unused
client-side command map, which had also drifted from the canonical launch
commands for continue, hermes, command-code, kiro, and mistral-vibe.
Claude-Session: https://claude.ai/code/session_014iufZnQwPD2obYuvdahjaE
Co-authored-by: kaynan <kaynan.camargo@terceiro-sky.com.br>
The claude/codex/openCode usage store slices read `scanState.enabled`
directly off `window.api.<provider>Usage.getScanState()`. In the web
client that usage IPC is not bridged, so the preload fallback proxy
resolves those calls to `undefined`, and enabling usage tracking from
Settings -> Stats & Usage throws
`TypeError: Cannot read properties of undefined (reading 'enabled')`
(reproduced live against `orca serve` v1.4.150; present on main too).
Guard the getScanState()/setEnabled() seams in all three slices so an
absent scan state degrades to a graceful no-op instead of crashing.
Desktop behavior is unchanged (a real ScanState is always truthy).
Adds a regression test that stubs the web-client fallback (every call
-> undefined) and asserts fetch*/enable* no-op without throwing for all
three providers.
Co-authored-by: ECO2G Migration <cmeia.ai02@cmeia.co.kr>
* fix(win): taskkill agent PTY descendant trees on stop
Windows agent teardown previously degraded to shell-only kill because
descendant snapshot is POSIX-only. Orphaned claude/codex/MCP children
kept worktree cwd handles open and blocked worktree remove.
Use taskkill /T /F on the PTY root in killWithDescendantSweep (daemon +
local agent shutdown) so the ConPTY tree is cleared before teardown.
Fixes#10004
* fix(win): bound taskkill with timeout and windowsHide
Prevent a wedged taskkill from delaying killRoot, and hide the console
flash during agent PTY tree teardown (CodeRabbit on #10100).
During scrollToIndex/reveal, rangeStartIndex can advance before TanStack
mounts the candidate Project row. Returning that unmounted index made a
Project sticky paint over the Host card in multi-host views. Prefer a
previous mounted sticky (or none for the group tier) until geometry exists.
Closes#10088
The theme picker count row concatenates "Showing {count}" directly with
the " of {{value0}}" fragment. The ko/ja/zh translations dropped the
fragment's leading separator, so the shown and total counts fused
(e.g. Korean rendered "표시 중 3030 중" instead of "표시 중 30/30").
Restore a slash separator for the total-count fragment and the leading
space for the search-match fragment in ko/ja/zh, in both the runtime
catalogs and the key-override sources so catalog regeneration keeps the
repaired values. Add a regression test covering both fragments.
🤖 Generated with Claude Code
`git sparse-checkout disable` restores the full working tree and sets
core.sparseCheckout=false, but deliberately leaves <gitdir>/info/sparse-checkout
in place so the checkout can be re-enabled with the same patterns.
detectSparseCheckout treated the mere presence of that pattern file as "sparse",
so a fully-populated worktree kept showing the sparse badge and the misleading
"Partial checkout. Files outside these paths are not on disk." tooltip.
Gate the fast-path fs.stat behind a config read that confirms core.sparseCheckout
is actually enabled (shared repo config or per-worktree config.worktree, honoring
git's precedence). The config read runs only when a non-empty pattern file
exists, so it does not reintroduce the per-poll subprocess fan-out PR #1290
removed, and it reads git's config files directly (no subprocess).
Adds a real-git regression test (enable -> disable leaves file -> not sparse) and
unit tests for the git-config boolean parser.
* fix(terminal): make Ctrl+V paste work in the HTTP web client
navigator.clipboard only exists in secure contexts, so the web client
served over plain HTTP (e.g. a LAN address) read an empty clipboard and
Ctrl+V silently did nothing: the keydown handler preventDefault-ed the
chord and suppressed the native paste event whose clipboardData is the
only clipboard access available there.
When the async clipboard reader is unavailable in the web client, let
the chord's native paste event fire and feed its clipboardData text
through the existing paste pipeline (size limits, bracketed-paste
handling and error toasts included).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RTjQWLg4wy1CgmcZKmGAuN
* fix: address review finding (bug-bash takeover)
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
RepoSettingsDraftInput holds unconfirmed IME text in local draft state and only
persists on compositionend (so the async store echo cannot cancel the composition).
If the user types a Hangul syllable and immediately navigates back out of project
settings while it is still composing, no compositionend fires — the last syllable
stays in the draft and never reaches the store, so the project's display name loses
its final character. Users have to click back into the field or press Enter (both
force compositionend) to make it stick.
Flush the visible draft on blur and on unmount, guarded by lastPersistedRef so it is
a no-op when a keystroke or compositionend already persisted that value. The flush
closure is published from an effect (post-commit) so a discarded concurrent render
cannot leave the cleanup pointing at uncommitted draft state.
Adds blur / unmount / no-double-persist / no-op regression tests to the component's
existing suite.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The AI Vault session scanner recorded a session's cwd by overwriting it with
every transcript record that carried one, so a session that changed working
directory mid-run ended up with its last-seen cwd. That value drives the resume
command (cd <cwd> && claude --resume <id>) and the history panel's grouping and
filtering.
For Claude this breaks resume: transcripts live under
~/.claude/projects/<slug-of-start-dir>/, and claude --resume <id> only searches
the project dir derived from the current cwd, so resuming from the drifted cwd
fails with "No conversation found". Capture the first-seen (start) cwd instead,
which transcripts write up front; this fixes resume, grouping, and filtering at
once. updateLatestLocation is only used by the Claude parser, so no other agent
is affected.
Fixes#9361
* fix(relay): honor the remote OpenSSH login shell for Windows SSH terminals (#9327)
* Prove OpenSSH DefaultShell fallback stays safe on bad registry reads
orchestration.ask blocks server-side until a reply lands or its timeout
(default 600s) elapses, holding the RPC open — but isLongPollRequest never
classified it as a long-poll. So the keepalive that resets the 30s
RUNTIME_RPC_SOCKET_IDLE_TIMEOUT_MS was never armed, and any ask left
unanswered for 30s died with a misleading runtime_unavailable ("The Orca
runtime closed the connection"), regardless of --timeout-ms. The same
omission left the handler's abort signal unwired (it is only passed for
long-polls), so the client-disconnect release path guarded by
signal?.aborted was dead code.
Add orchestration.ask to isLongPollRequest so it gets the keepalive, the
abort signal, and long-poll admission. The client already extends its
per-call socket timeout for ask (handlers/orchestration.ts passes
timeoutMs + 5s at the call site), so only the server-side classification
was missing.
The web client persists its workspace session (activeWorktreeId,
lastVisitedAtByWorktreeId, ...) in localStorage and, unlike the main-process
Store, gets no load-time orphan GC. `pruneLastVisitedTimestamps` already drops
stale focus-recency entries on hydration, but the persisted `activeWorktreeId`
pointer is not reconciled — so a pointer to a worktree the server no longer
reports lingers (the main-process path clears it via removeWorkspaceSessionOwner
when a repo is removed; the web has no equivalent), and can surface a phantom /
duplicate workspace that survives reloads.
Extend the hydration reconcile to also clear `activeWorktreeId` once its repo is
hydrated and the worktree is confirmed gone, mirroring the existing per-repo
defer rule (a not-yet-hydrated repo, e.g. SSH pre-connect, keeps its pointer).
Co-authored-by: fanyunqian.1 <fanyunqian.1@bytedance.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`hydrateHeadlessMobileSessionTabsFromWorkspaceSession` iterates
`workspaceSession.tabsByWorktree` with no repo gate. Those keys are
`${repoId}::${path}` and are not pruned when a repo disappears from this
client's view, so a stale key re-materializes a phantom "unknown"/duplicate
workspace with no live repo behind it, on boot, with no `session-created` event.
Skip entries whose parsed repoId is not in the live `repos`; leave unparseable
keys alone (conservative). Defensive complement to the delete-path (#9025) and
load-time (#9200) fixes — this closes the mobile/headless hydration path.
Co-authored-by: fanyunqian.1 <fanyunqian.1@bytedance.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`worktrees:persistSortOrder` (ipc/worktrees.ts) and `persistManagedWorktreeSortOrder`
(orca-runtime.ts) call `store.setWorktreeMeta(id, { sortOrder })` for every id the
renderer sends. `setWorktreeMeta` has no repo-existence check, so a stale id the
client still lists — e.g. a removed repo's `${repoId}::${path}` that lingers in the
renderer's order — gets a brand-new `worktreeMeta` entry minted on every sidebar
snapshot, resurrecting an orphan/duplicate workspace on the next launch.
A sort-order snapshot must only reorder worktrees that already exist, never create
one. Guard both call sites with `getWorktreeMeta` so absent ids are skipped.
Co-authored-by: fanyunqian.1 <fanyunqian.1@bytedance.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The managed token refresh is deferred while a live claude session owns
the credentials, but nothing reacted to the last live PTY exiting: the
deferred result is a terminal-classified error whose retry lane backs
off to the 15-minute cadence and is window-focus-gated, so the usage
panel stayed on "Waiting for Claude session" long after the blocker was
gone.
Notify on the live-PTY 1->0 transition (covering both markClaudePtyExited
and confirmSeededClaudeLivePtys releasing the last dead seeded id) and
force a claude-only refetch iff the current state is deferred.
Closes#9324
The Windows CLI shim is delivered via extraResources to
resources/bin/orca.cmd, beside the native resources/bin/orca.exe, and
resolves the launcher adjacent to itself (%SCRIPT_DIR%orca.exe) — which
works.
But nothing in `files` excluded resources/win32/, so its source copy was
also packed into app.asar and then extracted by asarUnpack:['resources/**']
to app.asar.unpacked/resources/win32/bin/orca.cmd. That duplicate has no
adjacent orca.exe, so invoking it fails with "Unable to locate the native
Orca CLI launcher", breaking orchestration skills that reach for the
unpacked shim.
Exclude the win32 shim source tree from app.asar so only the working
extraResources copy ships. Add a regression guard to the electron-builder
config test.
Closes#7351
* fix: prune workspace session state when a project is removed
removeProject → pruneWorktreeStateForRepo cleared worktreeMeta, lineage,
and workspace lineage, but left the removed repo's worktrees behind in
workspaceSession (lastVisitedAtByWorktreeId, sleepingAgentSessionsByPaneKey,
tab state, …) and in every workspaceSessionsByHostId partition.
Those dangling references caused the runtime to treat the deleted repo's
worktrees as recently-active on the next launch and re-materialize their
worktreeMeta. With no owning project left, the UI rendered them as an
orphaned "unknown" workspace that returned after every restart.
Reuse the existing removeWorkspaceSessionOwner cleanup (already used by
removeFolderWorkspace and deleteProjectGroup) against the legacy session
blob and each per-host partition, collecting owner keys before the
worktreeMeta delete loop so host classification still works.
Fixes#9024
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix: scope session cleanup to the removed host in removeProjectForHost
The workspace-session cleanup applied every collected owner key to the legacy
(local) session blob AND every workspaceSessionsByHostId partition. Owner keys
are `${repoId}::${path}` and carry no host, so a host-scoped removal
(removeProjectForHost) of one host wrongly wiped a surviving host's session for
a shared repo id/path — discarding its lastVisitedAt, tabs, sleeping-agent
state, and active-worktree pointer.
Scope the session prune to the removed host: prune the legacy blob only when the
target host is local (or on a full removeProject where hostId is null), and
prune only the matching workspaceSessionsByHostId partition for a non-local
host; a full removal still clears every partition. Collect session owner keys by
`${repoId}::` prefix (not belongsToHost, which is worktreeMeta-host-classified)
so a partition-only key is still prunable.
Adds regression tests for a shared repo id + path across local and an SSH host,
covering removeProjectForHost on the SSH host (local session survives) and on
the local host (SSH session survives).
Addresses review feedback on #9024.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test: cover third surviving host in removeProjectForHost session prune
Add a regression case where a shared repo id + path exists on local and two
SSH hosts. Removing one non-local host must prune only that host's session
partition, leaving both the legacy/local session and the other surviving SSH
host's partition intact. Locks in that the host-scoped cleanup never touches a
non-targeted third partition (per review feedback on #9024).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* perf: prune workspace session in a single clone+scan per partition
Project removal collected every owner key to prune, then called
removeWorkspaceSessionOwner once per owner AND per host partition. Each
call structuredClones and scans the entire session, so removal was
O(worktrees x hosts x session size) in full-session clones on the main
thread.
Add removeWorkspaceSessionOwners(session, ownerKeys) which clones once
and scans each collection once for the whole set, and route
pruneWorktreeStateForRepo through it. The per-owner O(1) field deletes
and the single-scan pane-key passes are factored into shared functions
so removeWorkspaceSessionOwner keeps identical behavior for its other
callers. No behavior change.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: PannenetsF <fanyunqian.1@bytedance.com>
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
* fix(terminal): stop middle-click primary paste inserting twice on X11
When "Middle-click Paste from Selection" is enabled, the integrated
terminal reads the X11 PRIMARY selection on mousedown and writes it to
the PTY itself. It calls preventDefault on the mousedown, but Chromium's
native X11 middle-click primary paste fires on mouse release regardless,
landing in xterm's helper textarea, which xterm then forwards to the PTY
a second time. The result is the selection pasted twice.
The global primary-selection hook already suppresses the follow-up native
paste, but only for editable DOM targets it owns via a pending-target
handle; it deliberately excludes xterm, so the terminal path had no
suppression at all.
Arm a short shared suppression window when the terminal handles a
middle-click, and have the global hook's capture-phase beforeinput/paste
suppressor honor it. This swallows the single native paste event so the
selection reaches the PTY exactly once. Ctrl+Shift+V and right-click
paste are unaffected: they use the CLIPBOARD and never emit a native
primary-paste event.
Closes#8860
* docs(terminal): clarify native middle-click paste suppression
* fix(terminal): scope primary-selection native-paste suppression to Linux xterm
Gate the armed native-paste suppression window to Linux (X11 primary-selection
path) and scope it to xterm's helper textarea so unrelated document pastes and
non-Linux platforms are never affected. Re-arm on auxclick for slow releases.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Orca <help@stably.ai>
When the macOS Computer Use helper app is missing (e.g. a dev build that
never ran `pnpm build:computer-macos`), getComputerUsePermissionStatus
reports helperUnavailableReason alongside all permissions set to
not-granted. The onboarding runner only checked the permissions, so it
called openSetup, which throws a RuntimeClientError in the
computerUsePermissions:openSetup IPC handler and logs an alarming error
to the main process console.
Guard on helperUnavailableReason first: surface the reason as a warning
and skip openSetup, so onboarding degrades gracefully instead of
triggering an IPC handler error.
The "Add a setup script" card cached its inspection result and only re-ran
on activeRepo/settings/sidebarOpen/dismiss/retry changes. A shared orca.yaml
setup hook that became effective on disk — edited externally, or run during
worktree creation — left the stale prompt visible until a full sidebar reopen.
Extract the revalidation into useSetupScriptPromptRevalidation, which
re-inspects on window focus (external edits / terminal hook runs) and when a
worktree of the repo activates while the prompt still shows no effective setup.
Claude-Session: https://claude.ai/code/session_01C8RPZ1mhUCMWcojgD6jLaN
Co-authored-by: kaynan <kaynan.camargo@terceiro-sky.com.br>
* fix(agent-status): replay remote Codex startup snapshots
* fix(agent-status): move unattributed-track comment next to its call
The '// Why: ... track the adoption/routing failure separately' comment
was left above the new replay branch after the track() call moved into
the non-replay branch. Move it next to track('agent_hook_unattributed')
so the rationale sits with the code it explains.
---------
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>