[upstream #10346] fix(win): tree-kill local PTYs so ports free on terminal stop - #146
[upstream #10346] fix(win): tree-kill local PTYs so ports free on terminal stop#146innocarpe wants to merge 1070 commits into
Conversation
…tablyai#12191) The parent-drift repair path (destroyPersistentWebview with preserveViewport: true, introduced in stablyai#12137) tears down and rebuilds the webview under the same browserTabId, but unconditionally forgot the tab's explicit user zoom. BrowserPane then re-seeds from the Settings default on the next mount, silently resetting per-tab zoom. Only forget explicit zoom on a real close; a preserveViewport rebuild keeps the same logical tab, so its zoom must survive.
…ver a bare renderer PTY (stablyai#12197) A mobile New Tab -> Codex create resolves the launch command and hands it to the renderer, but when the renderer's startup queue is lost (the stablyai#7587 stall class) the pane spawns a plain shell and the create still settles ready via PTY adoption - silently binding the phone to a bare terminal forever, since the ready status also disables the stablyai#7837 activation-time materialize recovery. Record the resolved launch command on the pending create and, at every renderer-backed settle point, deliver it to the adopted PTY when no spawn command was recorded for it. Spawn commands are noted per PTY by both spawn IPC handlers, so a missing record on the locally registered live PTY proves the launch never ran; delivery types the command exactly like the create would have, and the note prevents double delivery. Fixes STA-3214
…r breadcrumbs (stablyai#12198) * feat(crash-reports): add byte attribution to renderer memory highwater breadcrumbs Entry counts stay flat when a slice grows by value weight (97b9e86d leaked ~700MB while its biggest slice grew by 4 entries), so highwater breadcrumbs now carry sampled per-slice KB estimates for the store plus a live pane census (managers, panes, estimated scrollback KB) — the dominant heap cost the store census cannot see. * fix(crash-reports): bound renderer OOM profiling * fix(crash-reports): total raw store estimate bytes
…tablyai#12194) * fix(browser): bound retained webview guests across worktree switches * fix(browser): merge duplicate imports flagged by import/no-duplicates * fix(browser): evict guests without unmounting the worktree surface Unmounting the surface disposed every parked terminal byte watcher for the evicted worktree (bell/title/agent-completion facts dropped for good after the 15s handoff TTL) and skipped the force-park scrollback capture remote panes rely on. Destroy the guests only: hidden slots mount no BrowserPane, so nothing resurrects a destroyed guest before the next visit, and no slot ever unmount-detaches a live guest (STA-3228). Terminal-state vetoes and the eviction re-render bump are no longer needed; the only veto left is a guest an automation/mobile controller is actively driving. * fix(browser): veto eviction for downloading pages; keep zoom; add kill switch Main cancels a page's active downloads when its guest unregisters (tab-close semantics), so eviction now vetoes worktrees with a page still writing a download, tracked app-wide because download state was pane-local and hidden panes are unmounted. Eviction is not a user close: re-remember explicit zoom past the destroy-path forget so a revisit reasserts the user's zoom instead of resetting same-host siblings through Chromium's partition-wide HostZoomMap. browserGuestWorktreeRetentionBudget mirrors the terminal budget kill switch.
…yai#12233) * fix(terminal): route remote-runtime link clicks to the system browser Terminal link clicks classified ownership from the global activeRuntimeEnvironmentId, which is null when runtimes are bound per workspace, so a link clicked in a remote-hosted pane opened a local-only Orca browser tab and never reached the host. Thread each pane's resolved runtimeEnvironmentId into openHttpLink as sourceOwner across the OSC 8, WebLinksAddon, and click-fallback paths. Co-authored-by: Orca <help@stably.ai> * fix(terminal): route link clicks based on pane ownership, not global sta Clicking links on remote-hosted panes was routing based on global runtime state, causing unexpected reconnections. Now link routing decisions (where to open: Orca vs system browser) are based on the actual pane's owner — local, SSH connection, remote runtime, or unknown — regardless of whether any runtime is globally active. This ensures a local pane can route to Orca while another pane's remote runtime is active, and a remote pane always routes to the system browser. --------- Co-authored-by: Orca <help@stably.ai>
…tablyai#12103) (stablyai#12204) * fix(onboarding): run skill setup in the configured Windows runtime (stablyai#12103) Onboarding was the one skill-setup surface that did not route its install command through the resolved runtime. Settings, the feature-wall panels and the Linear prompt all wrap theirs as `wsl.exe -d <distro> -- sh -c ...` and pass a matching shell override; onboarding spawned a bare terminal and handed it the raw `npx skills add ...`. With Node inside WSL, npx is not on the Windows PATH, so the install failed. The runtime resolver had a second gap behind that: it only consulted per-project settings, and onboarding runs before any project exists. With no project it returned undefined and fell through to the Windows host, ignoring a global WSL default entirely. `getLocalAgentPreflightContext` already had a no-project fallback for PATH detection; the skill-install path had none. - extract that fallback as `getGlobalWindowsExecutionRuntimeContext` and rewire the existing agent-preflight branch through it so the two cannot drift - adopt it in `useActiveProjectSkillRuntime` when no project is active. WSL only: a windows-host default already matches the old no-project behavior, and resolving it would hand skill discovery a target where it had none, re-triggering scans for every host-default user - build the onboarding terminal's command for the runtime and pass its shell override - register the CLI in WSL rather than on the host, so `orca` lands on the PATH the install actually runs on, and wrap the copied command to match * fix(onboarding): keep skill setup runtime consistent * test(onboarding): satisfy runtime settings contract --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
…rktrees (STA-3123) (stablyai#12235) * fix(mobile): surface worktree catalog failures instead of showing 0 worktrees (STA-3123) A connected host whose worktree.ps request fails now shows an explicit catalog-failure state (with the RPC error code) on the host page, and 'Worktree list unavailable' on the home host card, instead of silently rendering as a healthy host with zero workspaces. * fix(mobile): mark cached worktree catalogs unavailable
stablyai#12209) Co-authored-by: Orca <help@stably.ai>
…#11987) * fix(terminal): expand variables in Windows PATH * fix(terminal): preserve expanded Windows PATH at spawn --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
Adapted from stablyai#11273 commits c690ee6, 5065240, and cc69fa7.
Adapted from stablyai#11616 commit 9f449d7 and stablyai#12120 commit 290f42a. Co-authored-by: holdn2 <club.makersfarm@gmail.com>
… Enter-keyup newline On Windows, the Enter-keyup synthesis path inferred the modified-Enter chord from release-time modifier state. A plain committing Enter (Process/229, no modifiers) followed by a rolled-over Shift for the next doubled consonant made the keyup report shiftKey=true and synthesized a Shift+Enter the user never chorded; a directly-sent Shift+Enter could likewise send a second newline from its keyup once the next composition started. Record observed Enter keydowns (code -> timeStamp) and let the keyup synthesis run only for presses whose keydown the IME swallowed entirely; a balancing keyup that copies the keydown timeStamp keeps the evidence for the later physical release. Refs stablyai#11878 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A single slot per code let a rapid second Enter press go unguarded: the first release found a mismatched timeStamp, dropped the only entry, and the second release then synthesized the Shift+Enter this guard exists to prevent. Track one entry per press and drain exactly one per physical release, so every press stays guarded until its own release. Auto-repeat keydowns do not stack an entry, since the whole run ends in one release, and the list is bounded so a press whose release never arrives cannot grow it without end. Refs stablyai#11878 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adapted from the physical Windows event trace contributed on stablyai#11878.
Backspacing away an entire Pinyin preedit ended the composition with empty data, no textarea residue, and no input/keypress events — yet _sendPendingComposition fell back to the last non-empty compositionupdate data and typed its first character into the PTY. Only trust that fallback when observed input evidence corroborates it; a composition with no evidence in any channel was cancelled. Fixes the macOS Pinyin regression from stablyai#11293 (stray letter left after deleting a preedit); same fix covers IBus/fcitx Backspace cancellation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bound exclusive host navigation to a generation-aware latest-wins single-flight so bulk open and switch fan-out stay responsive on large remote fleets. Add freeze repro harnesses and navigated settlement.
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> Co-authored-by: yoke233 <yoke2012@gmail.com>
…ablyai#12278) Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com> Co-authored-by: Hyunggyun Lyou <hg.lyou@miraeasset.com>
… snapshots (stablyai#12207) * test(runtime): prove mobile session publication rebuilds every worktree buildMobileSessionTabSnapshots consults its per-worktree cache after building the content, so a republish saves the fanout but none of the work. With 300 worktrees, an unchanged republish still does 601 units of per-worktree work, and a single changed worktree does 602. Publication is keyed on agentStatusByPaneKey/agentStatusEpoch, so this runs on every agent status tick. On a multi-client runtime host with 381 worktrees this allocated ~350 MB/min and rode the renderer into repeated 4 GB OOMs. Tests are marked it.fails so the branch stays green; drop .fails when the build loop skips worktrees whose inputs are unchanged. * refactor(runtime): make mobile session snapshot inputs explicit per worktree Every per-worktree builder in buildMobileSessionTabSnapshots took the whole AppState, so a worktree's real input set was the transitive closure of seven helpers and could not be memoized safely. Introduce MobileSessionWorktreeInputs — built once per worktree — and thread it through the group projection and the terminal/markdown/file/browser tab builders so the compiler proves the input set. Tab- and pane-keyed slices are narrowed to this worktree's tab ids, file ids, browser workspace/page ids, and pane keys; agent statuses are bucketed per worktree once per publication via a tab-id index. No behavior change. Dropping AppState from the projection path also removes the second per-worktree read of browserTabsByWorktree, so the publication-cost counter falls from 601 to 1 per publication and its two cases now pass. * fix(runtime): skip unchanged worktrees before building mobile session content buildMobileSessionTabSnapshots consulted its per-worktree cache only after building that worktree's three Maps, group projection, and full tab array, so the cache suppressed the fanout but none of the computation. Every agent-status tick therefore rebuilt every worktree, which drove sustained 4 GB renderer working sets on a host holding 381 worktrees. Cache MobileSessionWorktreeInputs alongside each snapshot and reuse the snapshot when every input field is reference-equal, before any intermediate structure is allocated. Worktrees with a mounted TerminalPane always rebuild: their live DOM/PaneManager state is invisible to store references. Absent per-worktree slices now resolve to shared empty values so an empty worktree can compare equal to its last publication. jsonContentEquals stays as the backstop on the rebuild path for inputs that churn by reference without changing output. With 300 worktrees, per-worktree content builds go from 300 to 0 on an unchanged republish and from 300 to 1 when one worktree changes. * test(runtime): cover agent status publication cost --------- Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
* fix(terminal): recover degraded daemon spawn routing * fix(terminal): preserve fresh-session recovery semantics * fix(terminal): avoid retaining exited recovery sessions
* Add branch line total chip to source control header Display the total lines added and removed across a branch from its fork point, measured via `git diff <mergeBase>`. Only computed when the chip is visible (request gate on merge base OID), with 500ms soft deadline to protect status latency and 15s hard timeout. Deduplicated across concurrent pollers and cached alongside line stats. Omitted on failure — always shows exact or nothing, never a partial estimate. Updates throughout the stack: native git status, relay, renderer store/API, and UI components. * Pin branch line total to app locale Format line counts using the app's configured locale instead of the system locale, ensuring consistent cross-platform display and test reliability. * test: wait for coalescer joins instead of fixed sleep Hold the diff until the second status pass actually takes the branch-total coalescer lease instead of using a fixed 400ms sleep. Fixes timing-dependent flakiness on slow machines.
- Replace 'Send answer' with 'Submit' for clarity and consistency - Update all locale translations (en, es, ja, ko, zh) - Remove fixed button width and add whitespace-nowrap for flexible sizing - Update component and test references
* Reorder source control to show staged changes first by default Stages are closest to the commit action and most relevant to the commit workflow. Merges untracked files into Changes visually while preserving their Git area. Removes the untracked-first preset and includes migration logic for existing user settings. * Drop source control group order user preference Remove the sourceControlGroupOrder setting and related UI, migrations, and persistence logic. The source control view now always displays sections in the order: staged changes, unstaged changes, untracked files. * Reorder source control to show changes before staged Aligns with the edit-stage-commit workflow by showing unstaged changes (active edits) before staged changes (queued for commit).
…12646) * Display SSH worktrees immediately using persisted metadata Users can now see known worktrees for SSH hosts without waiting for the provider connection to establish. Worktrees are fetched from local metadata and displayed as non-authoritative, then merged without replacing richer live data once the provider becomes available. * Show SSH folder workspaces immediately via persisted metadata Add safeguards for metadata fallback: track authoritatively removed worktrees per host to prevent resurrection, position new rows within the host block to avoid jumping on authoritative scan arrival, and preserve co-owner detection status during merge. Coalesce concurrent metadata fetches to dedupe overlapping queries.
Add a new census module that tracks pending and retained OSC sequences across all active PTY output processors. Each processor registers a gauge at creation and unregisters it on dispose, detach, or destroy — this prevents retained gauges from inflating later heap high-water profiles and allows the memory profiler to detect stalled processors as a sign of leaks.
…isting surface (stablyai#11576) * fix(file-explorer): sort numbered file names naturally The File Explorer compared names with bare localeCompare, so numbered files listed 100, 200 before 99. Hoist the numeric collator Source Control file rows already use (stablyai#10850) into src/shared and apply it to the local and runtime directory listings, the name-filtered view, and Source Control directory nodes, which were inconsistent with the file rows one line below (stablyai#11426). * fix(file-explorer): natural sort on SSH funnels, relay, and pickers Adversarial-review round 1 rework: - Both readDir funnels short-circuited to the SSH filesystem provider before the patched sort, so SSH workspaces kept lexicographic order; re-sort locally after the provider returns (the remote relay may be an older build), and fix the relay's own comparator for relay-native consumers. - sortDirEntries (shared, unit-tested) owns the directories-first + natural-order listing contract used by every funnel. - compareFileNames breaks numeric-collation ties ('2' vs '02') by code units so sibling order stays total instead of readdir order, and pins the collator locale to 'en' so every host produces one order. - The SSH folder browser and runtime server dir picker now match the Explorer they browse into. - Ordering pinned by tests at the relay, source-control tree, and shared helper. * fix(mobile): natural sort in the mobile file explorer Mobile re-sorted host readDir results with bare localeCompare, undoing the host funnel's natural order (round-2 review). Reuse the shared comparator and pin the order in the mobile suite. * fix(file-explorer): natural sort at the renderer choke point and remaining ties Round-3 review: the remote-runtime RPC and paired-web routes return the host's order verbatim, so re-sort in readFileExplorerDirectory where every desktop route converges; pin the SSH funnel with a handler-level test; and route Source Control path compares through compareFileNames so numeric-collation ties share one total order with the Explorer. * docs(file-name-sort): state the real perf baseline in the hoist comment * refactor(source-control): drop the dead collator export; pin the test oracle locale * fix(file-listings): cover remaining natural-sort surfaces
…ve (stablyai#12791) `orca serve` publishes a ready graph under HEADLESS_RUNTIME_WINDOW_ID with no BrowserWindow behind it. `shouldCreateInBackground` only degraded when the create was renderer-backed, so any focus-requested create fell through to getAuthoritativeWindow() and threw "No renderer window available" — leaving `terminal create --focus` with no workaround on a remote server (stablyai#10333). With a worktree selector and no renderer window, a background spawn is the only usable path, so collapse the renderer-backed window check into a plain "no window" check. That is the existing rendererBacked clause plus exactly the missing focus case, and it drops the confusing `rendererWindow === null` indirection (rendererWindow is already gated on rendererBacked). Focus is not lost by the degrade: the spawned pane is still published to the session-tab model and revealed with `activate: true`, which is how a paired client learns about it. Mirrors the in-tree precedent in runCreateMobileSessionTerminal. Headed hosts are unaffected — the clause only fires when no window exists. Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com>
…ablyai#12495) * fix(mobile): keep the cached transcript visible while reconnecting A manual retry closes the client and opens a fresh one, so the chat session hook saw a new client under an unchanged identity, dropped its settled read, and handed out an empty list — the transcript collapsed to a full-screen spinner until the swapped client's snapshot landed. Hold the last settled list per identity (captured post-commit) and keep rendering it while the re-read is in flight. `transcriptLoading` still gates consumers that decide from an empty transcript, so the launch-draft seed is unaffected. The held list is keyed by a new `sourceIdentity` (host/workspace) in addition to agent/session/transcript, so it can never serve another source's messages. Refs STA-3333. * test(mobile): assert the whole reconnect window, not just its first frame The re-subscribe lands a commit after the first render of the swap, so a regression that cleared the held list there left frame 0 green and still blanked the transcript. Verified: clearing the cache in the subscribe cleanup now fails this test, where before only the view-toggle test caught it. * fix(mobile): don't derive a tappable ask card from the held transcript The cache this PR adds keeps the previous list rendered while a swapped client re-reads. useMobileNativeChatPrompts was the one consumer reading `messages` without honouring `transcriptLoading`, so an ask answered on the terminal resurrected as a live, tappable card during that window. Gating on `transcriptLoading` is exactly base behaviour: `setRead` only ever stores 'ready'/'error', so status==='loading' implied an empty list before this PR. The live `askFromStatus` path is untouched. * chore: keep merge formatting scoped
…ed socket (stablyai#12790) The paired-runtime WS heartbeat terminated a client after a single unanswered 15s ping. One missed pong is UNKNOWN, not proof the peer is gone: a cellular or Tailscale blackhole, or a stalled TCP retransmit, routinely swallows one pong from a peer that is still there. Users on flaky paths saw constant drops, each costing a full redial plus E2EE re-handshake and subscription replay. Reap now needs MISSED_PROBE_LIMIT (3) consecutive unanswered probes, counted per socket rather than timed. Any proof of life -- pong or any inbound frame -- clears the count, as does a resume from a server-loop pause, since a gap the client was never given a chance to answer must not top up its budget. Missed sweeps still re-probe, so a recovered path proves itself on the next tick. Three matches the liveness budgets already in the product: the web client gives 45s (25s idle + 20s probe grace) and the relay control gives 75s. The paired transport's single miss was the outlier. Also gives the web client's redial the one-sided jitter the shared-control path already had, so a fleet dropped by one shared blip does not re-dial in lockstep; the helper is extracted to src/shared/reconnect-jitter.ts and shared by both. STA-3320, stablyai#12327 Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com>
…ablyai#12815) stablyai#12793 fixed PR status being hidden by workspace activity by relocating it: prDisplay was dropped from WorktreeCardStatusSlot and re-rendered as WorktreeCardReviewStatus in the title-row indicator group, at the right edge of the card. Reverting restores the review glyph to the left status lane. Because stablyai#12658 still narrows the passive-identity set to {inactive}, this alone brings PR status back only for inactive workspaces; reverting stablyai#12658 widens it to active and done. No behavior change for branch identity, and stablyai#8813's guard stays intact.
…oo (stablyai#12825) Stacked on the stablyai#12793 revert. Widens the passive-identity set from {inactive} back to {active, done, inactive}, so the PR/check glyph returns to the left status lane for workspaces that are actively being worked, not just idle ones. Tradeoff, deliberate: stablyai#12658 was not purely a regression. It also fixed stablyai#8813, where an active workspace with branch identity and no PR showed the grey branch glyph instead of the emerald Active dot. This revert reintroduces that, and removes its e2e guard. The left lane holds one glyph, so activity, branch identity, and review status cannot all be shown. This picks review status.
…stablyai#12841) Post-merge review of stablyai#12790 demonstrated a real leak: the resume-from-pause pardon cleared banked misses outright, so a host whose sweep stalls once every three ticks reset the budget forever and a dead socket was never reaped. The reviewer ran 300 sweeps against a permanently dead peer with a >1.5x gap every third tick and observed zero terminate() calls. Pre-stablyai#12790 that required a stall on *every* tick; the counter widened the pathological window 3x, and the failure mode is permanent non-reaping — the MAX_WS_CONNECTIONS leak the reaper exists to prevent. A stalled tick now charges no miss, which is all the original rationale needed (the client had no chance to answer that probe), but no longer forgives the misses already banked. A live client still clears its own count by answering the probe that is still sent on the stalled tick. The tolerance test's pause case is rewritten to assert the new contract rather than the old forgive-everything one, and a new test pins the leak directly: a host stalling every third tick must still reap a dead socket. Co-authored-by: Jinwoo-H <Jinwoo-H@users.noreply.github.com>
Sync update (
|
…tablyai#12776) * fix(terminal): stop transient probe blips from erroring restored panes (STA-3536) terminal_pane_owner_unverified fired for every restored pane whenever one liveness probe answer went missing: a cold-start daemon draining an attach stampede misses the 2s getSize deadline, and a wedged superseded daemon (protocol upgrades leave them running) turns every unmapped fan-out probe null forever. - probePtyOwners now skips legacy daemons whose startup inventory listing succeeded: fresh sessions never route to them, so they provably don't own an unmapped id and one wedged zombie can't poison every pane's verdict. - attachStablePaneOwner retries the probe over a short backoff ladder before surfacing unverified, so a single missed deadline resolves to a verdict. - The renderer replaces the raw error code with actionable copy. * fix(terminal): stop retrying definitive owner probes * fix(terminal): recover live panes after renderer restart * refactor(terminal): share owner resolution abort guard
…blyai#12775) * fix(mobile): keep hosts visible when credentials are unavailable * fix(mobile): guard unavailable host recovery * fix(mobile): protect replacement credential writes * fix(mobile): retain superseded cleanup intents * fix(mobile): preserve credential cleanup authority * fix(mobile): make host cleanup crash-safe
* fix(terminal): fence daemon endpoint ownership * fix(terminal): clean failed daemon PID claims * fix(terminal): close daemon ownership review gaps * test(daemon): release startup IPC in boot smoke * test(daemon): mirror production stdio in boot smoke * fix(daemon): exit after rpc shutdown cleanup * fix(terminal): make the socket name the daemon endpoint authority The reported failure was a live daemon hosting PTYs that nothing could reach: terminals acknowledged input and never ran it, listings diverged from reality, and restarting the app never helped because the detached helper survived. The ownership fence added for it could not fire in the sequence that produces the split brain. libuv unlinks the pathname a server bound to when that server closes, with no ownership check. A daemon that lost its endpoint name therefore deleted whichever socket then sat at that path — including a live replacement's — stranding a daemon that still hosted every session. Bind a private same-directory name and hard-link it into place instead: libuv can only ever unlink our own bind name, the exclusive link is a kernel-enforced endpoint claim, and the canonical name is removed only under an inode ownership check. The bind name replaces the basename rather than extending it, so it cannot overflow sun_path. killStaleDaemon removed the PID record unconditionally immediately before every fork, so the exclusive PID claim was always uncontested at bind time. It also unlinked a live daemon's endpoint whenever a connect probe merely timed out, and treated a `ps` timeout as proof of PID recycling. Now only positive evidence of a dead endpoint authorizes reclaiming it, SIGKILL is confirmed rather than assumed, and a daemon that cannot be proven stopped keeps its record and endpoint while the launcher refuses to fork beside it. A daemon whose endpoint was taken over now retires itself, draining rather than killing, so an unreachable orphan stops being permanent. A repaired PID record re-derives entryPath, appVersion and the Linux incarnation markers from the authenticated owner instead of dropping them; without appVersion a healthy daemon read as a permanently stale bundle and, on Windows, went unpinned against daemon-host pruning. Repair failure now fails open — abandoning a healthy daemon over a pid file write cost every persistent terminal on the machine. Also: treat only ENOENT as an unclaimed record so a Windows file lock is not reported as an ownership conflict; settle start() before close() so an accepted connection cannot defer it forever; sweep abandoned claim and bind names; and type the endpoint-identity seam so a rename cannot silently disable the fence. Adds a real-process handover smoke that reproduces the failure with two daemons racing one endpoint, and wires it into the native-smoke job. * fix(daemon): retire only on proven endpoint ownership loss The ownership watchdog read a null identity for any stat failure, so a transient EACCES or EIO on the runtime directory would retire a daemon that was still serving every terminal on the machine. Distinguish "the entry is gone" from "the probe failed" and act only on the former. Also require the loss to persist across two polls: a replacement publishes by unlink-then-link, and a single observation can land in that gap. * fix(daemon): source repaired ownership metadata from the authenticated hello Adversarial review found three defects in the previous two commits. Re-deriving entryPath from the owner's command line truncated it at the first space. A command line is a single space-joined string, so `C:\Program Files\Orca\...` and `/Applications/Orca 2.app/...` came back as `"C:\Program` and `/Applications/Orca`. getDaemonLaunchIdentity treats a present entryPath as authoritative, so a healthy daemon read as `different_app_path` and was killed and re-forked — worse than the missing-metadata case the derivation was added to fix. Carry entryPath and appVersion as optional fields on the daemon hello identity instead: the daemon already has both from its own argv, and per docs/reference/remote-wire-compatibility.md a new optional field is safe because every reader falls back when it is absent. This also removes a synchronous `ps` spawn from the Electron main thread during startup. `start()` rolled back the PID record even when it never published one. Losing the endpoint link now runs that path, and the ownership-checked unlink briefly renames the incumbent's record aside — enough to strand a live daemon's ownership. Roll back only what we actually wrote. publishDaemonSocketPath read its identity from the canonical name after linking, so a concurrent unlink returned null: no ownership watchdog and no endpoint cleanup on any shutdown path. Read it from the bound name before linking, which shares the inode. Refusing to fork beside an unconfirmed daemon left the user with no daemon at all and no in-app recovery, since restart re-entered the same fence. We have just proved something answers the endpoint, so adopt it in degraded mode: live sessions keep working, fresh terminals run locally. SIGTERM is also individually guarded now — an EPERM fell into the blanket catch and reported "nothing alive", authorizing the very duplicate this fence exists to prevent. Also reset the ownership-loss streak on an inconclusive probe so the confirmations are consecutive, and sweep scratch names before the launch so a failed launch still reclaims them.
On Windows, ConPTY shell-only kill left npm/dev-server children listening, so switching projects could still serve the previous app on the same port. - taskkill /T /F via killWithDescendantSweep for every local PTY stop on Windows - Ports UI kill path also tree-kills on Windows - Keep POSIX agent-only descendant sweep; plain POSIX terminals unchanged Closes stablyai#10150 Related: overlaps Windows agent path of stablyai#10004 / stablyai#10100
terminateWindowsProcessTree is best-effort and always resolves so PTY teardown is never blocked. Ports UI needs a real outcome: after taskkill, probe liveness with process.kill(pid, 0) and return failure if the PID is still alive. Addresses CodeRabbit review on stablyai#10183.
EPERM from process.kill(pid, 0) means the process is still alive without signal rights; do not report Ports kill success in that case. Addresses CodeRabbit follow-up on stablyai#10183.
… rebase cleanup No code change. Re-opens the contribution for review after branch recovery.
…ession tests Preserve WorkspacePortScanOptions on scanWorkspacePortProbes and re-add the authorizing re-scan metadata tests dropped during conflict resolution, while keeping Windows tree-kill liveness probes for stablyai#10150.
e8060f1 to
e94e4a8
Compare
Sync update (
|
Upstream
Summary
Summary - On Windows, every local PTY stop runs
taskkill /T /Fon the ConPTY root before shell kill, sonpm run dev/ Vite children release listening ports. - Ports panel kill also tree-kills on Windows instead of single-PIDprocess.kill. - POSIX keeps agent-only descenNote
innocarpe/orcamainuntil the upstream PR is merged.