[upstream #10183] fix(win): tree-kill local PTYs so ports free on terminal stop - #39
Closed
innocarpe wants to merge 371 commits into
Closed
[upstream #10183] fix(win): tree-kill local PTYs so ports free on terminal stop#39innocarpe wants to merge 371 commits into
innocarpe wants to merge 371 commits into
Conversation
Owner
Author
Sync update (
|
3 tasks
Owner
Author
Sync update (
|
…tablyai#10255) Co-authored-by: Orca <help@stably.ai>
…ai#10248) Co-authored-by: Orca <help@stably.ai>
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
…c EACCES injection (stablyai#10261)
…dy (stablyai#10157) An SSH connect whose relay deploy fails permanently (e.g. a dev build missing the platform relay package) drove an unbounded reconnect loop. doConnect creates the relay session ('idle'), then connectionManager. connect() drives the raw transport to 'connected' before the relay establishes. That premature 'connected' was forwarded to the renderer, which treats it as "host fully up": applySshConnectionStateChange bumps tab.generation for stranded remote panes -> remounts TerminalPane -> the pane connect gate re-invokes window.api.ssh.connect -> restarts the whole cycle forever (and spams connected-gated reads that fail with "Remote connection dropped"). Hold the premature 'connected' at 'deploying-relay' in onStateChange until the relay session is 'ready'. doConnect still broadcasts the authoritative 'connected' directly after establish() succeeds. Gate on connectInFlight so the hold is scoped to a live connect and never wedges a stray transport-blip 'connected' on a session left 'idle' by a relay version mismatch.
…yai#10246) * fix(terminal): hide SSH error toast under the reconnect banner The z-50 TerminalErrorToast was stacking over the non-blocking SSH reconnect banner with the raw ssh:connect failure. Suppress the toast while the banner owns recovery and clear matching toast text so it cannot flash after reconnect. * fix(terminal): strip only SSH-owned lines from aggregated terminal error onPtyError newline-joins multiple PTY errors into one string, so the prior startsWith() classification misfired on aggregated errors: an unrelated error before the SSH failure left the stale ssh:connect text to flash after reconnect, and an SSH-first error discarded any unrelated error. Classify per line and drop only reconnect-owned lines, keeping the rest. Closes CodeRabbit's aggregation edge. Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai>
…yai#8991) Preserve OMP session identity and exact resume paths across cold restoration, AI Vault, mobile, WSL/SSH, and host-authority routes. Add mixed-version fallback and completed-session recovery coverage.
…blyai#9973) * fix(terminal): re-verify cached macOS login-preflight rejections A conclusive PAM rejection was cached for the process lifetime, so one false verdict (the probe runs over pipes, not a PTY) disabled the login(1) TCC attribution wrapper for a daemon that survives app quits and updates for weeks — reintroducing the every-invocation AppData prompts stablyai#7003 fixed. Rejections now re-verify after 30 minutes; accepted verdicts still cache for the process lifetime. Refs stablyai#9756 * fix(daemon): replace hosts with stale login preflight cache Protocol 26 shipped the process-lifetime PAM rejection cache. Preserve its live sessions as a legacy generation, but route fresh terminals through protocol 27 so updating actually loads the expiring-cache fix. Refs stablyai#9756 * fix(terminal): validate rejected login probes under a PTY
…10266) * fix(worktree): match created worktrees through symlink roots On immutable Linux, /home is often a symlink to /var/home. git worktree list reports the realpath while Orca still holds the /home request path, so creation failed with "Worktree created but not found in listing". After local worktree add, fall back to realpath when string comparison misses. Keep WSL listings on string comparison only (host realpath is not authoritative there). Closes stablyai#10170 * test(worktree): harden symlink reconciliation authority * fix(worktree): reconcile creation by Git branch identity * test(worktree): reproduce symlink-root listing with real Git * test(worktree): cover cross-platform reconciliation * fix(worktree): keep reconciliation main-only --------- Co-authored-by: Wooseong Kim <innocarpe@gmail.com> Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
…blyai#10176) * fix(workspace-board): sync Linear on context-menu Move to Status The board's right-click "Move to Status" only wrote the local workspaceStatus and silently dropped the Linear sync that drag-and-drop performs. Thread an onAssignWorkspaceStatus callback from the drawer through the kanban card chain into WorktreeContextMenu so the menu funnels through the same local-first + Linear-sync path (moveWorktreesToStatus) as drag-and-drop. Outside the board (sidebar list) the menu keeps its local-only behavior. * test(workspace-board): guard context-menu Move to Status routing Extract the context-menu status-assign routing into a pure planWorkspaceStatusAssignment helper (behavior-preserving) and unit-test it, so the board Linear-sync vs sidebar local-only branch — the exact path stablyai#10175 regressed on — cannot silently flip back unnoticed. Covers board-sync-forwards-all-ids, local-only-writes-only-changed, and the same-status no-op case. Addresses code-review finding: the added drawer tests exercised the sync wiring via a mocked LaneGrid but never the menu's routing branch. Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: ElNelyo <ElNelyo@users.noreply.github.com> Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Co-authored-by: Orca <help@stably.ai>
…e (crashes d2c1da69, bb74236c) (stablyai#10271) Co-authored-by: Orca <help@stably.ai>
…n minimize→restore (stablyai#10158) * fix(terminal): stop the reveal fit from reflow-garbling inline TUIs grok (and other inline-viewport TUIs like Codex) render garbled after the floating terminal is minimized and brought back up. On reveal the resume path fit xterm synchronously right after re-attaching WebGL, whose cell metrics differ from the DOM renderer's — so it could propose a one-column-off grid, reflow xterm, then snap back a frame later (a net-zero resize "wiggle"). xterm's main-buffer wrap→unwrap is not a perfect inverse, and an inline TUI that diff-paints its pinned region redraws over the corrupted buffer. Replace the unconditional synchronous reveal fit (fitAllPanes) with a gated fit (PaneManager.fitAllPanesStable → fitRevealedPane): - fit synchronously only when the fit element's pixels actually changed while hidden (a real resize the app must reflow for anyway, kept ahead of the async PTY size reassert so it can't forward a stale grid); - if the pixels are unchanged but the grid diverged while hidden (snapshot / SSH-reattach direct terminal.resize, or an appearance/DPI change), repair it on a steady grid (requestStablePaneFit) so a sustained mismatch refits while a transient cell-metric wobble does not reflow; - otherwise leave the pane alone. The common minimize→restore is now a hard no-op with zero reflow. Also applied to the window-wake reveal path. * refactor(terminal): tighten reveal-fit comments + rename to fitAllRevealedPanes Quality pass: make fitRevealedPane the single canonical explanation of the reveal wobble and reduce the duplicated comments at the call sites to short pointers; rename PaneManager.fitAllPanesStable -> fitAllRevealedPanes ("stable" only described one of its three branches); symmetric early-returns in fitRevealedPane. No behavior change.
…er workspace (stablyai#10252) (stablyai#10268) * fix(worktree): don't path-sweep sibling sessions when deleting a folder workspace (stablyai#10252) Deleting one folder-workspace instance could kill terminal/agent sessions in OTHER workspaces sharing the same checkout path — sibling instances, and even worktrees of a different repo rooted under that directory. Both pi and Claude Code agent sessions died at once with no recovery. The `cwdOwned` path fallback in killAllProcessesForWorktree() derives its match path via splitWorktreeIdForFilesystem(), which strips the `::workspace:<uuid>` suffix and collapses a folder instance's path to the shared checkout dir. Every untagged session under that dir then path-matched and got swept (worst case: a home directory registered as a folder repo). Disable the path fallback for folder-workspace instances — their filesystem path can't identify a single instance. The exact `${worktreeId}@@` prefix and authoritative `session.worktreeId` matches (both carrying the instance uuid) still tear down the deleted instance's own sessions; normal git worktrees (unique paths) keep the fallback. The runtime and registry sweeps already matched by exact worktreeId. Adds isFolderWorkspaceInstanceId() and regression tests. See docs/delete-workspace-cwd-owned-sibling-kill.md. * rm design doc
…i#10193) * fix(remote): create paired agent sessions without host focus * test(remote): assert structured resume request * test(remote): preserve provider-separated resume coverage * test(remote): assert paired agent focus authority * fix(remote): separate agent host creation from viewer focus * test(remote): harden agent-session authority validation * test(remote): validate retired pane identity --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* fix: route folder workspaces in worktree operations (stablyai#10251) Folder workspaces are not Git worktrees and never appear in the repo/worktree catalogs, so they were falling through to unresolved cross-host routing and failing closed on all owner-dependent operations. Extract folder workspace ownership logic to a dedicated module and add dedicated routing for folder workspace identifiers before checking Git worktree catalogs. * persist folder workspace metadata on the FolderWorkspace record Folder workspaces lack worktreeMeta rows; metadata updates (activity bumps, unread status, terminal focus) must call updateFolderWorkspace. Fixes routing so local folder workspaces resolve to 'local' even when unrelated runtimes exist (stablyai#10251). * Fix folder workspace mutations routing through owners Folder workspace updates and deletions were routing through the currently focused runtime instead of the owning runtime. Add coordinators for concurrent-update race prevention and activity-persistence coalescing. Handle runtime-owned folders in editor file operations and terminal activity tracking.
Folder workspaces are a first-class workspace type that all changes must consider alongside git worktrees. Document this requirement for developers.
Point localized READMEs and in-app Android download CTAs at mobile-android-v0.0.32 (English README and orca-site were already updated).
…7650) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… branches (stablyai#7654) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tings order (stablyai#10801) The legacy Codex session-id rescan (used when a persisted record has no transcriptPath) returned the FIRST trusted home holding a rollout with that id. That home becomes the pane's CODEX_HOME — i.e. it picks the ACCOUNT — and the list ended in per-account homes ordered by settings INSERTION order, so the account was decided by whichever one the user happened to add first. Ranks instead: selected account -> real system home -> shared runtime mirror -> everything else by normalized path. Both ranking inputs are required (an optional one would silently degrade to pure path order), and the selection arrives as a thunk so the common provenance-present resume never stats the ownership marker for a ranking it never runs. The mirror needs its own tier: prepareLegacySharedCodexSessionResume's guard only fires when the resolved home IS the mirror, and that guard is what migrates the rollout into ~/.codex. Without it a system-default selection silently resumed under an arbitrary account and stayed pinned there permanently once the hook stamped a transcript path. Reviewed over two independent rounds; every tier individually mutation-proved. Live-validated in a real Orca dev build by reading the spawned PTY's actual CODEX_HOME across three builds (head, base, and head-minus-the-mirror-tier). Note: fixes none of stablyai#10757's user-visible symptoms on its own — it is a correctness precondition. Verified on macOS only; Windows coverage is fixture-only.
…lyai#10814) Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com> Co-authored-by: Neil <4138956+nwparker@users.noreply.github.com>
…lyai#10872) Closing Ctrl+F left one match highlighted until the window was minimized and restored. xterm's DecorationService keys its SortedList on `decoration.marker.line`, but `SortedList.delete()` only records an index and defers compaction, while `Marker.dispose()` sets `line = -1` — mutating that same sort key. After the first disposal the array is no longer sorted, so the binary search inside `delete()` can miss a decoration that is present. It returns false, `onDecorationRemoved` never fires, and the decoration stays live and keeps painting. Repaints don't help; they faithfully re-paint a live decoration, which is why only a window cycle appeared to fix it. `clearDecorations()` disposes the active match before the match highlights, which is exactly the order that trips this. Patch `delete()` to retry once after compacting pending deletions, on the miss path only, so the common bulk delete keeps its O(log n) search and deferred batching. A 3000-trial randomized differential against upstream semantics shows no behavior change for well-ordered lists.
…blyai#10883) Co-authored-by: Orca <help@stably.ai>
…blyai#10881) Co-authored-by: Orca <help@stably.ai>
…rkers, marketplace v0 (experimental) (stablyai#8549) * feat(plugins): Orca plugin system — kernel, content packs, panels, workers, marketplace v0 (experimental) Adds Orca's experimental plugin system behind a settings flag: a supervised kernel, declarative content packs (VM recipes, commands and keybindings, language packs), sandboxed iframe panels, forked worker hosts, and a Git-backed marketplace v0 with consent, provenance and kill-list enforcement. Theme, icon-theme and terminal-theme contributions are deferred to a follow-up pass. * fix(plugins): make unsupported marketplace listings unreachable by key findPlugin() backs preview/install/previewInstalledUpdate via requireListing(), so filtering only listPlugins() hid the catalog card while leaving the dead install path reachable one click later. * fix(plugins): fan Pi session-only status out to plugin subscribers The providerSessionOnly early-return in applyNormalizedStatus emitted to onAgentStatus (main-window fanout) but skipped enrichedStatusListeners, so plugins subscribed to agent.status.changed silently missed every Pi session_start event. Route both emit sites through one helper so a future early return cannot drop the plugin tap again. Co-authored-by: Orca <help@stably.ai> * plugins: drop dead code and hoist duplicated trust-boundary patterns Cleanup pass over the P1 diff, no behavior change: - Delete `readPluginTreeSnapshot`/`readSnapshotFile` and their types, plus the now-vestigial `directories`/`signal` plumbing in `collectFiles`. - Delete `resolveContainedPluginDirectory` (no callers). - Delete `plugin-content-load-pool.ts`; it reimplemented the existing `mapWithConcurrency`, whose index arg also removes the pairing wrapper in `buildPluginList`. - Hoist `PLUGIN_CONTENT_HASH_PATTERN` and `PLUGIN_COMMIT_PATTERN` into the install-lockfile module; 11 sites hand-rolled these identically. - Point the new reliability gate at the PR instead of gitignored docs paths, matching every other gate's link form. * fix(plugins): retry plugin state renames on Windows AV/EPERM locks Six plugin write paths (lockfile, provenance, current pointer, kill list, marketplace cache, staged install dir) did a plain rename, so an antivirus or indexer holding the target open surfaced as a failed install. The repo already retries this hazard for issue stablyai#1507, but only through a sync helper; these paths are all async. Adds one bounded async retry + atomic write used by all six, and trims a consent-provenance header that restated its own JSX. * test(plugins): cover the Windows rename retry path The retry loop shipped untested: both existing cases hit the non-retry path, and the temp-cleanup test passed identically with the `finally` removed. Mock `rename` to queue errno codes so CI can exercise locks it cannot provoke. Co-authored-by: Orca <help@stably.ai> * fix(plugins): pin bundled plugin resources to LF Windows CI checks out with autocrlf, so the byte-hashed launch tree arrived as CRLF and verify-packaged-plugin-resources rejected it — the packaged build could never pass on Windows. Reproduced locally: CRLF yields the exact CI error, LF verifies clean. Files are already LF, so nothing renormalizes. Co-authored-by: Orca <help@stably.ai> * test: guard the bundled-plugin LF pin against a CRLF checkout The byte-hash mismatch only surfaced in Windows packaging CI. Assert the .gitattributes pin and that a CRLF tree is rejected, so a regression fails on any platform instead of waiting for a packaged Windows build. Co-authored-by: Orca <help@stably.ai> * ci: trigger packaged-build check on bundled plugin resource changes The launch tree is byte-hashed during packaging, but no trigger path covered it — so the CRLF fix for that check would not have re-run the check. Add the resources, verifier and .gitattributes paths that can break packaging. Co-authored-by: Orca <help@stably.ai> * perf(plugins): rebuild the panel frame only when its baked theme values change The revision keys the panel iframe, so every bump destroys the sandboxed frame and its in-panel state. It counted root attribute mutations, but --workspace-sidebar-live-width is written every rAF of a sidebar drag, so dragging with a panel open blanked it ~60x/sec. Compare the two values the shell actually bakes in instead. Co-authored-by: Orca <help@stably.ai> * test: stop pinning a plugin name in the CRLF guard The CRLF case rewrites every launch file, so the reported mismatch is whichever plugin sorts first. P2 adds theme plugins that sort ahead of orca-navigation-shortcuts, which broke the assertion there. Co-authored-by: Orca <help@stably.ai> * style: drop stray blank lines left by the rebase resolutions Both sides of the agent-hooks and orca-runtime conflicts contributed a trailing blank, which oxfmt rejects. Whitespace only. Co-authored-by: Orca <help@stably.ai> * test(plugins): stop the startup budget failing on machine load P95 runs 16-34ms idle but exceeds the 50ms bound under full-suite parallelism, so the gate flaked. Widen it to catch an order-of-magnitude regression instead; the no-worker/no-plugin-code assertions are the real guarantee. Verified a 400ms regression still fails. Co-authored-by: Orca <help@stably.ai> --------- Co-authored-by: Orca <help@stably.ai>
…8255) * fix(remote): accelerate shared-control and pane recovery on resume/online Narrow stablyai#8255 onto current main after stablyai#9774: fire pending shared-control reconnect timers and pane recovery backoffs on system resume and browser online, without replacing the per-pane recovery state machine or reconnect banner UX. * test(remote): cover online and occluded-resume recovery triggers * fix(remote): centralize recovery acceleration --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
* fix(mobile): block iOS uploads below the last shipped App Store version The closed-train guard looked up each candidate version's own App Store record, but a version only gets one once it is submitted for review. 0.0.34 reached TestFlight and was never submitted, so it had no record, nothing looked closed, and the patch-bump walk stopped there — while 0.0.35 had already shipped. Apple rejected the upload after a 24-minute build (90186 closed train, 90062 needs a higher CFBundleShortVersionString). Fetch the highest closed version once and treat everything at or below it as closed, comparing semver numerically so 0.0.10 outranks 0.0.9. Also read appVersionState alongside appStoreState: the latter is deprecated in App Store Connect API 3.3 and renames the shipped state to READY_FOR_DISTRIBUTION, so reading only the old field would silently find zero closed versions once Apple stops populating it. * chore(mobile): prepare 0.0.36 app.json sat at 0.0.32 while 0.0.35 shipped on the App Store, because release versions are resolved on the runner and never committed back. Close the four-version drift so the checked-in version matches reality and the iOS release no longer depends on the closed-train walk to find an open version. Bump Android versionCode 8 -> 9 in the same commit: the version is shared between platforms, and shipping 0.0.36 with the code that already shipped for 0.0.32 produces an APK that cannot install over the released build.
Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
…acter (stablyai#10866) * perf(agent-status): strip terminal control bytes by run, not per character stripTerminalControl built its result with a per-character `+=`, allocating a fresh string for every retained character. The Command Code status detector calls it four times per PTY chunk — the scan text, the chunk-boundary variant, and both previous-text lengths — so an agent pane paid that on every write. Control bytes are sparse in real output, so copy the spans between them instead: 2.3x-2.6x from 5 KiB to 106 KiB chunks. Output is byte-identical, checked exhaustively over every string up to length 4 across a 13-symbol control/unicode alphabet plus 200k random strings (224,831 inputs, 0 mismatches). * docs(agent-status): condense the run-copy rationale comments Review feedback: both comments walked through the implementation. Keep one line of non-obvious rationale each, per the repo's comment guidelines. Co-authored-by: Orca <help@stably.ai> * test(agent-status): correct terminal strip benchmark * test(agent-status): bound terminal strip benchmark --------- Co-authored-by: Orca <help@stably.ai>
…ablyai#10892) Co-authored-by: Orca <help@stably.ai>
* fix(terminal): restore link hover after mouseleave * test(terminal): verify mouseleave listener cleanup * test(terminal): assert link hover listener wiring
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.
… tests CodeRabbit: platform overrides leaked when expects threw before the end-of-test restore; wrap bodies so cleanup always runs.
Address CodeRabbit: TerminateProcess can leave a PID queryable until handles drain; probe a few times before reporting failure. Platform restore already runs in finally via withPlatform.
innocarpe
force-pushed
the
fix/windows-terminal-port-tree-kill
branch
from
July 27, 2026 15:24
274473f to
1ad4b84
Compare
Owner
Author
|
Upstream stablyai#10183 closed without merge — closing portfolio mirror. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.