Skip to content

fix: close readiness gaps found by merged-change audit - #17159

Merged
nwparker merged 45 commits into
mainfrom
nwparker/ssh-readiness-audit
Aug 31, 2026
Merged

nwparker merged 45 commits into
mainfrom
nwparker/ssh-readiness-audit

Conversation

@nwparker

@nwparker nwparker commented Aug 29, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 126 +11256 -1018 +10238
Prod 158 +7870 -1556 +6314

Summary

This branch closes the SSH/session-tab race gaps found in the readiness audit and fixes #12115. It preserves remote PTY ownership across reconnect/restart, prevents stale shutdown/remount callbacks from mutating replacement panes, and keeps split-pane PTY bindings keyed by durable leaf identity instead of reusable numeric pane slots.

The same ownership fencing covers delayed SSH attach/reattach, transport stream errors, teardown cleanup, and queued startup consumption. Existing headless tab order/title fixes remain included for the directly related parked-tab regressions.

Fixes #17144, #12115, #17297, and #15710.

Behavior contract

  • A remote disconnect is unverifiable, never evidence that the PTY exited. Only a confirmed process exit publishes exited.
  • PTY shutdown, stream callbacks, reattach results, layout writes, and teardown cleanup are admitted only for the current transport/leaf incarnation.
  • Split panes retain independent PTY ownership; a sibling no longer inherits the tab-level source PTY during parked-tab materialization.
  • A stale fresh-spawn callback cannot consume the successor pane's one-shot startup.

Verification

Final pushed head: e202461110290cea32650984044096ccb988bc1b (rebased on origin/main 212c0e42a6518bc558ba75a61f8b76af64b894d2).

  • pnpm tc — passed after final patch.
  • pnpm run check:code-quality:changed — 0 new findings (including type-aware quality and React Doctor).
  • oxfmt --check and git diff --check — passed.
  • Focused ownership/reattach/lifecycle suite — 68 passed.
  • Expanded SSH spawn/reattach/queued-startup/lifecycle matrix — 97 passed.
  • Electron E2E build (electron-vite build --mode e2e) — passed.
  • Parked CLI split E2E (terminal-parked-cli-split.spec.ts) — passed 1/1 on the final build.
  • A prior SKIP_BUILD=1 run against a production build timed out before test setup because window.__store is intentionally exposed only by the E2E/development build; the correctly built E2E run passed.

Readiness checklist

Section Result Evidence / residual
SSH / remote / network Pass with host evidence below Reconnect, cancellation, delayed catalog, relay ownership, mixed-version, and stale-callback paths are covered; live-host checks are recorded in this PR.
Crash / retry / growth Pass Retry ladders and ownership generations are bounded; no new unbounded structures were introduced. Pre-existing environment tombstone maps remain a follow-up P2.
Security / supply chain Pass No dependency or network-sink changes; existing guarded process/shell boundaries remain in force.
Performance / resources Pass No new polling/fanout; callbacks are constant-time identity checks.
Functional correctness Pass #12115/#17297 split/park/reconnect paths are covered by regression tests and E2E.
Backward compatibility / data loss Pass Layout bindings remain optional persisted state; no migration or incompatible wire opcode.
Cross-platform / remoting Pass with recorded gaps macOS, Linux/SSH, and Windows-host checks are recorded; physical WSL reboot/sleep-resume remains unavailable.
Code organization Pass Domain-specific modules/tests; no max-lines bypass or generic catch-all module.

Scope and residual issues

This PR is the merge candidate for #17159; it does not claim every open GitHub tab report is fixed. Unrelated/provider-specific/mobile tab reports remain separate follow-ups. The only known release-validation gap is physical WSL reboot/sleep-resume coverage.

@nwparker
nwparker requested a review from brennanb2025 as a code owner August 29, 2026 10:17
@coderabbitai

coderabbitai Bot commented Aug 29, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The changes add keyboard-interactive SSH authentication with abortable credential requests and updated verification UI. SSH install locks track host boot identity, and relay reattachment refuses to revive tombstoned surfaces. PTY shutdown and daemon session control now enforce incarnation fencing, and the runtime service fences handle replacement and rejects duplicate graph tab IDs. Remote workspace sync waits for delayed worktree placement, retries stale snapshot applications, and validates upload authority. Terminal tab registration and CLI split routing are now worktree-scoped, and web-session terminal orphan recovery is restructured into dedicated modules. Native PTY handling, platform awake assertions, pricing, editor state, cleanup, and CI workflow caching receive related updates, with corresponding unit and end-to-end test coverage.

Merge Risk: 🟡 Moderate · up to 69e1b

The PR improves SSH terminal lifecycle, authentication, remote-workspace synchronization, terminal routing, and Windows PTY handling, but the current head still has bounded correctness and failure-handling risks that could misclassify reconnect failures, misroute terminal state, falsely report workspace synchronization, suppress unrelated daemon errors, or allow incomplete hydration validation. Merge should wait for these issues to be fixed or explicitly accepted.

🚥 Pre-merge checks | ✅ 1 | ❌ 4

❌ Failed checks (4 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The changes address the core requirements for #17144, #12115, #17297, and major parts of #15710. However, directly linked issue #17117 requires a Runtime-validated, capability-gated identity-proof pri… Implement the #17117 identity-proof requirements, or remove the direct link and any claim that this PR fixes the issue. Also confirm that all claimed #15710 acceptance criteria, including the remote-runtime update path, are either implement…
Out of Scope Changes check ⚠️ Warning The PR includes changes unrelated to the linked issue objectives, including macOS sleep assertion behavior, quick-open directory cleanup, deletion of the Linux proc scanner, Sonnet 4.6 pricing, Node-n… Split unrelated fixes into separate pull requests with their own linked issues, or add explicit linked issue context that justifies each change. Keep this PR limited to the SSH lifecycle, terminal hydration and split recovery, remote-runtim…
Docstring Coverage ⚠️ Warning Docstring coverage is 15.69% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 51 functions across 51 files. (1 skipped:… Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The description gives detailed scope, behavior contracts, linked issues, verification results, residual risks, and test evidence. However, it omits several template sections, including the required Vi… Add the missing template sections. Include visual proof or write N/A with a reason if no visual or interaction change applies. Add the required checklist items and mark their status. Include AI Disclosure, Agent skill upstream boundary, N…
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately describes the pull request's primary purpose: fixing readiness gaps identified by a merged-change audit.
Full details: Linked Issues check

Explanation

The changes address the core requirements for #17144, #12115, #17297, and major parts of #15710. However, directly linked issue #17117 requires a Runtime-validated, capability-gated identity-proof primitive with begin/complete CLI and challenge semantics, and the PR explicitly states that this issue is out of scope and does not implement those requirements.

Resolution

Implement the #17117 identity-proof requirements, or remove the direct link and any claim that this PR fixes the issue. Also confirm that all claimed #15710 acceptance criteria, including the remote-runtime update path, are either implemented or separately tracked.

Full details: Out of Scope Changes check

Explanation

The PR includes changes unrelated to the linked issue objectives, including macOS sleep assertion behavior, quick-open directory cleanup, deletion of the Linux proc scanner, Sonnet 4.6 pricing, Node-next native caching, skill-guide cleanup, Markdown styling, and other unrelated UI and test changes.

Resolution

Split unrelated fixes into separate pull requests with their own linked issues, or add explicit linked issue context that justifies each change. Keep this PR limited to the SSH lifecycle, terminal hydration and split recovery, remote-runtime reconciliation, and directly required supporting changes.

Full details: Docstring Coverage

Explanation

Docstring coverage is 15.69% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 51 functions across 51 files. (1 skipped: 1 unsupported.)

Full details: Description check

Explanation

The description gives detailed scope, behavior contracts, linked issues, verification results, residual risks, and test evidence. However, it omits several template sections, including the required Visual Proof section, explicit ELI5/What Changed/Why sections, AI Disclosure, Agent skill upstream boundary, Notes, and the final Checklist.

Resolution

Add the missing template sections. Include visual proof or write N/A with a reason if no visual or interaction change applies. Add the required checklist items and mark their status. Include AI Disclosure, Agent skill upstream boundary, Notes, and clear ELI5, What Changed, and Why content.

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@nwparker
nwparker force-pushed the nwparker/ssh-readiness-audit branch from 7220d4a to 362bbc3 Compare August 29, 2026 10:20
@greptile-apps

greptile-apps Bot commented Aug 29, 2026 •

Copy link
Copy Markdown

Greptile Summary

This remediation PR strengthens SSH lifecycle, authentication, remote-workspace hydration, and terminal-platform behavior, with supporting regression coverage.

  • Adds incarnation-fenced SSH shutdown and pending-kill replay handling.
  • Adds cancellable keyboard-interactive authentication and renderer prompting.
  • Waits for late worktree placement and retries stale remote snapshot preparation.
  • Contains Windows ConPTY input-pipe failures and updates supporting reliability coverage.

Confidence Score: 4/5

The PR is not yet safe to merge because snapshot retry exhaustion can still strand a connected SSH target in pulling state with its remote tabs unavailable.

The attempted fix retries stale snapshot preparation only three times and then returns without applying the snapshot, updating sync state, or arranging another reconciliation.

Files Needing Attention: src/renderer/src/hooks/remote-workspace-target-sync.ts

Important Files Changed

Filename Overview
src/renderer/src/hooks/remote-workspace-target-sync.ts Adds bounded stale-token retries, but exhausting all attempts still leaves the current snapshot unresolved and the target pulling.
src/renderer/src/hooks/remote-workspace-snapshot-apply.ts Returns explicit apply outcomes and waits for late worktree placement before merging remote terminal state.
src/renderer/src/hooks/remote-workspace-snapshot-placement.ts Adds bounded, subscription-driven worktree-path placement checks scoped to the SSH execution host.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Receive current remote snapshot] --> B[Apply with preparation token]
  B -->|Applied or failed| C[Resolve sync state]
  B -->|Stale| D[Refresh catalog and prepare]
  D --> E{Attempts remaining?}
  E -->|Yes| B
  E -->|No| F[Current code returns silently]
  F --> G[Target remains pulling and unhydrated]
Loading

Reviews (4): Last reviewed commit: "test(wire): pin pre-metadata release cov..." | Re-trigger Greptile

Comment on lines +138 to +174
for (let attempt = 0; attempt < MAX_SNAPSHOT_APPLY_ATTEMPTS; attempt += 1) {
const result = await applyDirectSshRemoteWorkspaceSnapshot({
store: deps.store,
snapshot,
token: applyToken,
arrival,
isArrivalCurrent,
isPreparationTokenCurrent: deps.isPreparationTokenCurrent,
waitForWorkspaceSessionReady,
finalizeHydratedTerminals: deps.finalizeHydratedTerminals
})
if (result !== 'stale' || !isArrivalCurrent(authority.targetId, arrival) || stopped) {
return
}
const input = await deps.capturePreparationInput(
authority,
'workspace-snapshot',
snapshot.revision
)
if (!input || !isArrivalCurrent(authority.targetId, arrival)) {
return
}
const prepared = await deps.prepareOnly(input)
if (
!prepared.token ||
!deps.isPreparationTokenCurrent(prepared.token) ||
!isArrivalCurrent(authority.targetId, arrival)
) {
return
}
const refreshedToken = buildDirectSshSnapshotApplyToken(prepared.token, snapshot.revision)
if (!refreshedToken) {
return
}
applyToken = refreshedToken
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Snapshot retries end silently

When the worktree catalog changes during all three preparation attempts, the final stale result falls through without applying the snapshot, updating the sync status, or scheduling another reconciliation. The target remains pulling and unhydrated, so its remote tabs remain unavailable until another snapshot event or reconnect occurs.

Knowledge Base Used:

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 2ee90cce-35d3-48ef-aa98-17e9cc890110

📥 Commits

Reviewing files that changed from the base of the PR and between 51ed7d4 and 07953d9.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (70)
  • .github/workflows/node-next-compat.yml
  • config/patches/node-pty@1.1.0.patch
  • config/reliability-gates.jsonc
  • config/scripts/generate-bundled-skill-guides.test.mjs
  • config/scripts/pr-workflow-parallelism.test.mjs
  • skill-guides/orca-per-workspace-env.md
  • src/cli/bundled-skill-guides.ts
  • src/main/agent-awake-service-platform-assertions.test.ts
  • src/main/agent-awake-service.test.ts
  • src/main/agent-awake-service.ts
  • src/main/claude-usage/claude-model-pricing.test.ts
  • src/main/claude-usage/claude-model-pricing.ts
  • src/main/claude-usage/store.test.ts
  • src/main/daemon/daemon-entry.ts
  • src/main/daemon/daemon-native-pty-exception.test.ts
  • src/main/daemon/daemon-native-pty-exception.ts
  • src/main/daemon/node-pty-windows-input-error.win32.test.ts
  • src/main/ipc/ssh-connection-state-callbacks.ts
  • src/main/ipc/ssh-passphrase.test.ts
  • src/main/ipc/ssh-passphrase.ts
  • src/main/ipc/ssh.ts
  • src/main/macos-system-sleep-assertion.ts
  • src/main/persistence-ssh-pending-pty-kill.test.ts
  • src/main/persistence/leasing-ssh-ptys/ssh-pty-kill-intent-operations.ts
  • src/main/persistence/loading-store/pty-binding-persistence.ts
  • src/main/providers/pty-provider-contract.ts
  • src/main/providers/ssh-pty-provider.test.ts
  • src/main/providers/ssh-pty-provider.ts
  • src/main/ssh-reattach-pane-cardinality.test.ts
  • src/main/ssh/ssh-connection-auth-fallback.test.ts
  • src/main/ssh/ssh-connection-gssapi-fallback.test.ts
  • src/main/ssh/ssh-connection-host-key-store-wiring.test.ts
  • src/main/ssh/ssh-connection-test-harness.ts
  • src/main/ssh/ssh-connection-utils.ts
  • src/main/ssh/ssh-connection.test.ts
  • src/main/ssh/ssh-connection.ts
  • src/main/ssh/ssh-multi-key-authentication.test.ts
  • src/main/ssh/ssh-pending-pty-kill-replay.test.ts
  • src/main/ssh/ssh-pending-pty-kill-replay.ts
  • src/main/ssh/ssh-relay-cross-version-isolation.test.ts
  • src/main/ssh/ssh-relay-install-lock-commands.ts
  • src/main/ssh/ssh-relay-install-lock.ts
  • src/main/ssh/ssh-relay-session-pending-kill-replay.test.ts
  • src/main/ssh/ssh-relay-session-reconnect-incarnation.test.ts
  • src/main/ssh/ssh-relay-session-test-fixtures.ts
  • src/main/ssh/ssh-relay-session.ts
  • src/main/ssh/ssh-relay-upload-cancel.docker.test.ts
  • src/main/ssh/ssh-remote-commands.test.ts
  • src/preload/api/ssh-api.ts
  • src/preload/index.ts
  • src/relay/pty-handler-retired-pane-surface.test.ts
  • src/relay/pty-handler.ts
  • src/renderer/src/app-shell/remote-workspace-unplaced-upload-suppression.test.tsx
  • src/renderer/src/components/editor/EditorMarkdownFileSurface.tsx
  • src/renderer/src/components/editor/EditorPanel.tsx
  • src/renderer/src/components/editor/editor-panel-render-model.test.ts
  • src/renderer/src/components/editor/editor-panel-render-model.ts
  • src/renderer/src/components/settings/SshPassphraseDialog.tsx
  • src/renderer/src/hooks/remote-workspace-snapshot-apply.ts
  • src/renderer/src/hooks/remote-workspace-snapshot-placement.ts
  • src/renderer/src/hooks/remote-workspace-target-sync.test.ts
  • src/renderer/src/hooks/remote-workspace-target-sync.ts
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/store/slices/ssh.ts
  • src/shared/linux-proc-socket-owner-scanner.test.ts
  • src/shared/linux-proc-socket-owner-scanner.ts
  • src/shared/quick-open-directory-reader.ts
  • src/shared/quick-open-readdir-walk.test.ts
  • src/shared/ssh-pending-pty-kill.ts
  • tests/e2e/ssh-cold-hydration-gap-tab-seeding.spec.ts
💤 Files with no reviewable changes (2)
  • src/shared/linux-proc-socket-owner-scanner.test.ts
  • src/shared/linux-proc-socket-owner-scanner.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 0 remain after this review.

Comment on lines +191 to +194
setTimeout(
() => emitSshEvent('error', new Error('Timed out while waiting for handshake')),
readyTimeout
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Scope mock lifecycle events to each MockSshClient.

eventHandlers is a module-level map, but the new pending timeout and destroy() emit through emitSshEvent. If a reconnect or test creates more than one client, one client's timeout or close event is delivered to every client's listener. The pending timer also remains active after destroy(). This can reject or close the wrong connection and make lifecycle tests pass or fail for the wrong reason. Store handlers and the handshake timer on each MockSshClient, emit only to that client's handlers, and clear the timer in destroy().

Also applies to: 212-212

Comment on lines +1429 to +1431
const error = new Error('Timed out while waiting for SSH authentication')
Object.assign(error, { level: 'client-timeout' })
onStartupError(error)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

#!/bin/bash
# Description: Check how transient-error classification treats timeout errors.
set -euo pipefail

fd -t f 'ssh-connection-utils.ts' | while IFS= read -r f; do
  rg -n -C 12 'isTransientError|isTransientReconnectError|client-timeout|Timed out' "$f"
done

# Also find any other definitions of these predicates.
rg -nP -C 12 'function (isTransientError|isTransientReconnectError)\b' --type=ts

Repository: stablyai/orca

Length of output: 1008


🏁 Script executed:

#!/bin/bash
set -euo pipefail

utils="$(fd -t f 'ssh-connection-utils.ts' | head -n 1)"
printf '%s\n' "=== $utils ==="
cat -n "$utils" | sed -n '1,125p'

printf '%s\n' '=== predicate definitions and call sites ==='
rg -n -C 8 'isTransientError|isTransientReconnectError' src/main/ssh --type ts

printf '%s\n' '=== changed timeout path ==='
cat -n src/main/ssh/ssh-connection.ts | sed -n '1390,1460p'

printf '%s\n' '=== applicable repository conventions ==='
find /tmp/coderabbit-repo-knowledge/stablyai-orca-89dc44e4 -maxdepth 2 -type f -name '*.md' -print \
  | while IFS= read -r f; do
      case "$f" in
        */conventions/*ssh*.md|*/conventions/*SSH*.md|*/architecture/*ssh*.md|*/architecture/*SSH*.md|*/learnings/*ssh*.md|*/learnings/*SSH*.md)
          printf '%s\n' "=== $f ==="
          cat "$f"
          ;;
      esac
    done

Repository: stablyai/orca

Length of output: 40511


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '=== reconnect classifier ==='
cat -n src/main/ssh/ssh-reconnect-error-classification.ts | sed -n '1,78p'

printf '%s\n' '=== connect retry path ==='
cat -n src/main/ssh/ssh-connection.ts | sed -n '650,715p'

printf '%s\n' '=== startup error wiring and reconnect path ==='
rg -n -C 14 'onStartupError|runReconnectAttempt|connect\(' src/main/ssh/ssh-connection.ts

Repository: stablyai/orca

Length of output: 24812


Classify the replacement timeout as transient.

isTransientError ignores level: 'client-timeout' and does not match Timed out while waiting for SSH authentication. isTransientReconnectError also does not match this message. Both paths can therefore enter the terminal error state instead of retrying.

Comment thread src/main/ssh/ssh-relay-session.ts Resolved
[
`cat ${shellQuote(`${remoteRelayDir}/.install-lock/.boot-id`)}`,
`cat ${shellQuote(`${remoteRelayDir}/relay.js`)}`,
`find ${shellQuote('/root/.orca-remote')} -maxdepth 1 -name 'relay-reboot-lock-oracle.tombstone.*' -print | wc -l | tr -d ' '`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Check the actual tombstone location.

tryStealInstallLockCommand creates ${remoteRelayDir}/.install-lock.tombstone.*. This command scans /root/.orca-remote for relay-reboot-lock-oracle.tombstone.*, so it always reports zero. Scan ${remoteRelayDir} for .install-lock.tombstone.*.

Proposed fix
- `find ${shellQuote('/root/.orca-remote')} -maxdepth 1 -name 'relay-reboot-lock-oracle.tombstone.*' -print | wc -l | tr -d ' '`
+ `find ${shellQuote(remoteRelayDir)} -maxdepth 1 -name '.install-lock.tombstone.*' -print | wc -l | tr -d ' '`
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
`find ${shellQuote('/root/.orca-remote')} -maxdepth 1 -name 'relay-reboot-lock-oracle.tombstone.*' -print | wc -l | tr -d ' '`
`find ${shellQuote(remoteRelayDir)} -maxdepth 1 -name '.install-lock.tombstone.*' -print | wc -l | tr -d ' '`
🧰 Tools
🪛 ast-grep (0.45.2)

[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { execFileSync, spawnSync } from 'node:child_process'
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)

initialToken: DirectSshSnapshotApplyToken
): Promise<void> => {
let applyToken = initialToken
for (let attempt = 0; attempt < MAX_SNAPSHOT_APPLY_ATTEMPTS; attempt += 1) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Snapshot retries still end silently

When catalog or preparation-token changes make all three snapshot application attempts stale while the arrival remains current, this loop exits without applying the snapshot, resolving the pulling status, or scheduling reconciliation. The target remains unhydrated with its remote tabs unavailable until another snapshot event or reconnect occurs.

Knowledge Base Used:

@greptile-apps

greptile-apps Bot commented Aug 30, 2026 •

Copy link
Copy Markdown

Too many files changed for review (284 files, 100 file limit).

Bypass the limit by tagging @greptile-apps to review.

@coderabbitai

coderabbitai Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@nwparker
nwparker force-pushed the nwparker/ssh-readiness-audit branch from c5872e5 to 03531c7 Compare August 30, 2026 10:09

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 13

🧹 Nitpick comments (4)
tests/e2e/helpers/paired-terminal-restart-renderer-probes.ts (1)

170-178: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Exclude stack from the deduplication key.

The key includes the stack value. A store subscription runs from different dispatch call sites, so the captured stack text can change while the binding state stays the same. The probe then records duplicate transitions and transitions grows during the long restart test. Build the key from the state fields only, and keep stack as diagnostic data.

♻️ Proposed refactor
-      const key = JSON.stringify(transition)
+      const { stack: _stack, ...state } = transition
+      const key = JSON.stringify(state)
       if (key !== probe.lastKey) {
         probe.lastKey = key
         probe.transitions.push(transition)
       }
tests/e2e/paired-remote-terminal-serve-restart-binding.spec.ts (1)

587-594: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Compute invalidRestartTransitions at assertion time.

The filter runs at Line 587, but the assertion runs at Line 634. The probe phase stays 'restart' until Line 640, so transitions recorded during the post-restart output, resize, and keyboard steps are captured by the probe and never inspected. Re-read the transitions immediately before the assertion so the invariant covers the whole restart phase.

♻️ Proposed refactor
-    const invalidRestartTransitions = restartTransitions.filter(
-      (transition) =>
-        !transition.tabPresent ||
-        !transition.layoutPresent ||
-        transition.binding === null ||
-        transition.bindings.length === 0 ||
-        transition.tabPtyId !== transition.binding
-    )
+    const collectInvalidRestartTransitions = async () =>
+      (await readPairedTerminalBindingTransitions(client!.page)).filter(
+        (transition) =>
+          transition.phase === 'restart' &&
+          (!transition.tabPresent ||
+            !transition.layoutPresent ||
+            transition.binding === null ||
+            transition.bindings.length === 0 ||
+            transition.tabPtyId !== transition.binding)
+      )

Then use it at the assertion site:

     expect(
-      invalidRestartTransitions,
+      await collectInvalidRestartTransitions(),
       'A pending-handle publication erased or removed the verified mirrored binding'
     ).toEqual([])
src/renderer/src/hooks/remote-workspace-snapshot-apply.ts (1)

162-169: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the repeated session-import step.

Lines 146-150 and 165-169 build remoteSession with identical options. Only the collected unplacedTabWorktreePaths array differs. A local closure removes the copy and keeps the two passes in sync if the option set grows.

♻️ Proposed refactor
+  const importAgainstCurrentState = (): {
+    state: AppState
+    worktreeIds: ReadonlySet<string>
+    unplaced: string[]
+    remoteSession: ReturnType<typeof importRemoteWorkspaceSession>
+  } => {
+    const nextState = store.getState()
+    const nextWorktreeIds = resolveDirectSshSnapshotWorktreeIds(nextState, authority)
+    const unplaced: string[] = []
+    return {
+      state: nextState,
+      worktreeIds: nextWorktreeIds,
+      unplaced,
+      remoteSession: importRemoteWorkspaceSession(snapshot.session, {
+        resolveWorktreeId: uniqueWorktreeIdByPath(nextWorktreeIds),
+        executionHostId: toSshExecutionHostId(authority.targetId),
+        onUnplacedTerminalTabs: (worktreePath) => unplaced.push(worktreePath)
+      })
+    }
+  }
src/main/runtime/orca-runtime-terminal-close-continuity-graph-fixture.ts (1)

147-149: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Keep canary rows in syncSplitFixtureGraph.

When includeCanary is true, this helper drops canarySessionLayout, canarySyncedTab, and canarySyncedLeaf. Preserve the existing layouts and include the canary rows as the other graph helpers do.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 025f9da6-f136-4065-82cf-f0d68045f447

📥 Commits

Reviewing files that changed from the base of the PR and between 7f822a7 and c5872e57e13def17267a68278d3a832eb57ee520.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (260)
  • .github/workflows/node-next-compat.yml
  • config/patches/node-pty@1.1.0.patch
  • config/reliability-gates.jsonc
  • config/scripts/generate-bundled-skill-guides.test.mjs
  • config/scripts/pr-e2e-gate-contract.test.mjs
  • config/scripts/pr-e2e-source-routing.mjs
  • config/scripts/pr-workflow-parallelism.test.mjs
  • skill-guides/orca-per-workspace-env.md
  • src/cli/bundled-skill-guides.ts
  • src/cli/handlers/terminal.test.ts
  • src/cli/handlers/terminal.ts
  • src/main/agent-awake-service-platform-assertions.test.ts
  • src/main/agent-awake-service.test.ts
  • src/main/agent-awake-service.ts
  • src/main/claude-usage/claude-model-pricing.test.ts
  • src/main/claude-usage/claude-model-pricing.ts
  • src/main/claude-usage/store.test.ts
  • src/main/daemon/daemon-attach-only-retirement.ts
  • src/main/daemon/daemon-entry.ts
  • src/main/daemon/daemon-native-pty-exception.test.ts
  • src/main/daemon/daemon-native-pty-exception.ts
  • src/main/daemon/daemon-pty-adapter-cold-restore-reanchor.test.ts
  • src/main/daemon/daemon-pty-adapter-protocol-compatibility.test.ts
  • src/main/daemon/daemon-pty-adapter-replacement-exit-race.test.ts
  • src/main/daemon/daemon-pty-adapter-session-adoption.test.ts
  • src/main/daemon/daemon-pty-adapter.test.ts
  • src/main/daemon/daemon-pty-adapter.ts
  • src/main/daemon/daemon-pty-applied-size.ts
  • src/main/daemon/daemon-pty-connection-lifecycle.ts
  • src/main/daemon/daemon-pty-runtime-state.ts
  • src/main/daemon/daemon-pty-session-control.ts
  • src/main/daemon/daemon-pty-session-spawn.ts
  • src/main/daemon/daemon-pty-spawn-request.ts
  • src/main/daemon/daemon-pty-spawn-result.ts
  • src/main/daemon/node-pty-windows-input-error.win32.test.ts
  • src/main/ipc/pty/ipc/spawn-commit.ts
  • src/main/ipc/pty/pane/stable-pane-relay-absence-respawn.test.ts
  • src/main/ipc/pty/runtime/spawn-commit.ts
  • src/main/ipc/remote-workspace-cache.test.ts
  • src/main/ipc/remote-workspace-patch-queue.test.ts
  • src/main/ipc/remote-workspace-relay-sync.ts
  • src/main/ipc/remote-workspace-snapshot-cache.ts
  • src/main/ipc/remote-workspace.test.ts
  • src/main/ipc/remote-workspace.ts
  • src/main/ipc/ssh-connection-state-callbacks.ts
  • src/main/ipc/ssh-passphrase.test.ts
  • src/main/ipc/ssh-passphrase.ts
  • src/main/ipc/ssh.ts
  • src/main/macos-system-sleep-assertion.ts
  • src/main/persistence-ssh-pending-pty-kill.test.ts
  • src/main/persistence-ssh-remote-pty-binding-replay.test.ts
  • src/main/persistence/leasing-ssh-ptys/ssh-pty-kill-intent-operations.ts
  • src/main/persistence/loading-store/pty-binding-persistence.ts
  • src/main/persistence/loading-store/session-host-partitions.ts
  • src/main/persistence/loading-store/store-domain-composition.ts
  • src/main/persistence/loading-store/workspace-session-snapshot-publication.ts
  • src/main/persistence/loading-store/workspace-session-terminal-binding-replay.test.ts
  • src/main/persistence/loading-store/workspace-session-terminal-binding-replay.ts
  • src/main/providers/pty-provider-contract.ts
  • src/main/providers/pty-spawn-result.ts
  • src/main/providers/ssh-pty-provider.test.ts
  • src/main/providers/ssh-pty-provider.ts
  • src/main/runtime/mobile-session-terminal-retirement-proof.test.ts
  • src/main/runtime/mobile-session-terminal-retirement-proof.ts
  • src/main/runtime/mobile-session-terminal-retirement.ts
  • src/main/runtime/orca-runtime-tab-id-collision.test.ts
  • src/main/runtime/orca-runtime-terminal-close-continuity-fixtures.ts
  • src/main/runtime/orca-runtime-terminal-close-continuity-graph-fixture.ts
  • src/main/runtime/orca-runtime-terminal-close-continuity-state-fixture.ts
  • src/main/runtime/orca-runtime-terminal-close-continuity.test.ts
  • src/main/runtime/orca-runtime-terminal-handle-incarnation.test.ts
  • src/main/runtime/orca-runtime-terminal-retirement.test.ts
  • src/main/runtime/orca-runtime-terminal-split-authority.test.ts
  • src/main/runtime/orca-runtime.test.ts
  • src/main/runtime/orca-runtime.ts
  • src/main/runtime/rpc/methods/terminal/terminal-multiplex-cleanup.ts
  • src/main/runtime/rpc/methods/terminal/terminal-multiplex-connection.ts
  • src/main/runtime/rpc/methods/terminal/terminal-multiplex-flow-control.ts
  • src/main/runtime/rpc/methods/terminal/terminal-multiplex-frame-delivery.ts
  • src/main/runtime/rpc/methods/terminal/terminal-multiplex-live-stream.ts
  • src/main/runtime/rpc/methods/terminal/terminal-multiplex-slot-frames.ts
  • src/main/runtime/rpc/methods/terminal/terminal-multiplex-subscribe-frame.ts
  • src/main/runtime/rpc/methods/terminal/terminal-multiplex-subscribe-resolution.ts
  • src/main/runtime/rpc/terminal-multiplex-end-verdict.test.ts
  • src/main/ssh-reattach-pane-cardinality.test.ts
  • src/main/ssh/ssh-connection-auth-fallback.test.ts
  • src/main/ssh/ssh-connection-gssapi-fallback.test.ts
  • src/main/ssh/ssh-connection-host-key-store-wiring.test.ts
  • src/main/ssh/ssh-connection-test-client.ts
  • src/main/ssh/ssh-connection-test-harness.ts
  • src/main/ssh/ssh-connection-utils.test.ts
  • src/main/ssh/ssh-connection-utils.ts
  • src/main/ssh/ssh-connection.test.ts
  • src/main/ssh/ssh-connection.ts
  • src/main/ssh/ssh-multi-key-authentication.test.ts
  • src/main/ssh/ssh-pending-pty-kill-replay.test.ts
  • src/main/ssh/ssh-pending-pty-kill-replay.ts
  • src/main/ssh/ssh-reconnect-error-classification.test.ts
  • src/main/ssh/ssh-relay-cross-version-isolation.test.ts
  • src/main/ssh/ssh-relay-install-lock-commands.ts
  • src/main/ssh/ssh-relay-install-lock.ts
  • src/main/ssh/ssh-relay-session-pending-kill-replay.test.ts
  • src/main/ssh/ssh-relay-session-reconnect-incarnation.test.ts
  • src/main/ssh/ssh-relay-session-test-fixtures.ts
  • src/main/ssh/ssh-relay-session.ts
  • src/main/ssh/ssh-relay-upload-cancel.docker.test.ts
  • src/main/ssh/ssh-remote-commands.test.ts
  • src/main/window/runtime-window-lifecycle.ts
  • src/preload/api/ssh-api.ts
  • src/preload/api/ui-command-event-api.ts
  • src/preload/api/workspace-session-api.ts
  • src/preload/index.ts
  • src/relay/pty-handler-retired-pane-surface.test.ts
  • src/relay/pty-handler.ts
  • src/renderer/src/app-shell/reconcile-hydrated-workspace-tab-models.test.ts
  • src/renderer/src/app-shell/reconcile-hydrated-workspace-tab-models.ts
  • src/renderer/src/app-shell/remote-workspace-unplaced-upload-suppression.test.tsx
  • src/renderer/src/app-shell/use-app-session-persistence.ts
  • src/renderer/src/app-shell/use-app-startup-hydration.ts
  • src/renderer/src/components/Terminal.tsx
  • src/renderer/src/components/editor/EditorContent.markdown-classification.test.tsx
  • src/renderer/src/components/editor/EditorMarkdownFileSurface.tsx
  • src/renderer/src/components/editor/EditorPanel.tsx
  • src/renderer/src/components/editor/editor-panel-render-model.test.ts
  • src/renderer/src/components/editor/editor-panel-render-model.ts
  • src/renderer/src/components/floating-terminal/FloatingTerminalPanel.tsx
  • src/renderer/src/components/settings/SshPassphraseDialog.tsx
  • src/renderer/src/components/terminal-pane/TerminalErrorToast.tsx
  • src/renderer/src/components/terminal-pane/TerminalOverlaySlot.tsx
  • src/renderer/src/components/terminal-pane/TerminalPane.tsx
  • src/renderer/src/components/terminal-pane/TerminalPaneOverlayLayer.react185.test.tsx
  • src/renderer/src/components/terminal-pane/codex-detached-pane-restart.ts
  • src/renderer/src/components/terminal-pane/pty-connection-pty-exit-teardown.test.ts
  • src/renderer/src/components/terminal-pane/pty-connection-session-liveness.test.ts
  • src/renderer/src/components/terminal-pane/pty-connection-types.ts
  • src/renderer/src/components/terminal-pane/pty-connection/pty-exit-hibernate.ts
  • src/renderer/src/components/terminal-pane/pty-connection/session-reconcile-dispose.ts
  • src/renderer/src/components/terminal-pane/pty-connection/transport-output-callbacks.ts
  • src/renderer/src/components/terminal-pane/pty-transport-types.ts
  • src/renderer/src/components/terminal-pane/remote-runtime-pty-transport-attach-subscription.test.ts
  • src/renderer/src/components/terminal-pane/remote-runtime-pty-transport-end-verdict.test.ts
  • src/renderer/src/components/terminal-pane/remote-runtime-pty-transport-expired-pane-recovery.test.ts
  • src/renderer/src/components/terminal-pane/remote-runtime-pty-transport-pane-handle-resolution.test.ts
  • src/renderer/src/components/terminal-pane/remote-runtime-pty-transport-stale-handle-recovery.test.ts
  • src/renderer/src/components/terminal-pane/remote-runtime-pty-transport.ts
  • src/renderer/src/components/terminal-pane/remote-runtime-resubscribe-failure-recovery-routing.test.ts
  • src/renderer/src/components/terminal-pane/sleeping-record-park-exemption.test.ts
  • src/renderer/src/components/terminal-pane/sleeping-record-park-exemption.ts
  • src/renderer/src/components/terminal-pane/terminal-error-accumulation.test.ts
  • src/renderer/src/components/terminal-pane/terminal-error-accumulation.ts
  • src/renderer/src/components/terminal-pane/terminal-pane-recovery.test.ts
  • src/renderer/src/components/terminal-pane/terminal-pane-recovery.ts
  • src/renderer/src/components/terminal-pane/terminal-pane-split-request-routing.test.ts
  • src/renderer/src/components/terminal-pane/terminal-pane-split-request-routing.ts
  • src/renderer/src/components/terminal-pane/terminal-parked-pty-watcher.ts
  • src/renderer/src/components/terminal-pane/terminal-parked-tab-eviction-exemption.test.ts
  • src/renderer/src/components/terminal-pane/terminal-parked-tab-watchers.test.ts
  • src/renderer/src/components/terminal-pane/terminal-startup-grid-settle.test.ts
  • src/renderer/src/components/terminal-pane/terminal-startup-grid-settle.ts
  • src/renderer/src/components/terminal-pane/use-terminal-pane-lifecycle.ts
  • src/renderer/src/components/terminal-pane/use-terminal-tab-cold-parking.test.ts
  • src/renderer/src/components/terminal-pane/use-terminal-tab-cold-parking.ts
  • src/renderer/src/components/terminal/background-terminal-worktree-mount.test.ts
  • src/renderer/src/components/terminal/background-terminal-worktree-mount.ts
  • src/renderer/src/constants/terminal.ts
  • src/renderer/src/hooks/ipc-events/terminal-command-state.ts
  • src/renderer/src/hooks/ipc-events/terminal-presentation-ipc-bridge.ts
  • src/renderer/src/hooks/ipc-events/terminal-request-ipc-bridge.ts
  • src/renderer/src/hooks/ipc-events/terminal-ui-routing-ipc-bridge-split.test.ts
  • src/renderer/src/hooks/ipc-events/terminal-ui-routing-ipc-bridge.ts
  • src/renderer/src/hooks/ipc-tab-switch-group-order-hydration.test.ts
  • src/renderer/src/hooks/ipc-tab-switch.test.ts
  • src/renderer/src/hooks/ipc-tab-switch.ts
  • src/renderer/src/hooks/remote-workspace-push-status.ts
  • src/renderer/src/hooks/remote-workspace-session-readiness.ts
  • src/renderer/src/hooks/remote-workspace-snapshot-apply-deferred-session-write.test.ts
  • src/renderer/src/hooks/remote-workspace-snapshot-apply.ts
  • src/renderer/src/hooks/remote-workspace-snapshot-arrival-coordinator.test.ts
  • src/renderer/src/hooks/remote-workspace-snapshot-arrival-coordinator.ts
  • src/renderer/src/hooks/remote-workspace-snapshot-duplicate-tab-repair.test.ts
  • src/renderer/src/hooks/remote-workspace-snapshot-fresh-client-tab-seeding.test.ts
  • src/renderer/src/hooks/remote-workspace-snapshot-local-tab-survival.test.ts
  • src/renderer/src/hooks/remote-workspace-snapshot-placement.ts
  • src/renderer/src/hooks/remote-workspace-snapshot-unplaced-tab-adoption.test.ts
  • src/renderer/src/hooks/remote-workspace-target-sync-test-harness.ts
  • src/renderer/src/hooks/remote-workspace-target-sync.test.ts
  • src/renderer/src/hooks/remote-workspace-target-sync.ts
  • src/renderer/src/hooks/useIpcEvents-terminal-create-surfacing.test.ts
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/lib/mobile-terminal-tab-mount.test.ts
  • src/renderer/src/lib/mobile-terminal-tab-mount.ts
  • src/renderer/src/lib/workspace-activation-terminal-focus.test.ts
  • src/renderer/src/lib/workspace-activation-terminal-focus.ts
  • src/renderer/src/runtime/host-session-mirror-hydration-frame-ordering.test.tsx
  • src/renderer/src/runtime/remote-runtime-terminal-end-verdict.test.ts
  • src/renderer/src/runtime/remote-runtime-terminal-multiplexer-types.ts
  • src/renderer/src/runtime/remote-runtime-terminal-response-controller.ts
  • src/renderer/src/runtime/sync-runtime-graph-terminal-registration-ownership.test.ts
  • src/renderer/src/runtime/sync-runtime-graph.ts
  • src/renderer/src/runtime/web-runtime-session-browser-create-focus.test.ts
  • src/renderer/src/runtime/web-runtime-session-snapshot.ts
  • src/renderer/src/runtime/web-runtime-session-tab-activate-close.test.ts
  • src/renderer/src/runtime/web-runtime-session-test-harness.ts
  • src/renderer/src/runtime/web-runtime-session.test.ts
  • src/renderer/src/runtime/web-session-tabs-sync-mirror-identity.test.ts
  • src/renderer/src/runtime/web-session-tabs-sync-terminal-mirroring.test.ts
  • src/renderer/src/runtime/web-session-tabs-sync.test.ts
  • src/renderer/src/runtime/web-session-tabs-sync.ts
  • src/renderer/src/runtime/web-session-terminal-orphan-inventory-retry.test.ts
  • src/renderer/src/runtime/web-session-terminal-orphan-mixed-version.test.ts
  • src/renderer/src/runtime/web-session-terminal-orphan-recovery-adoption-regressions.test.ts
  • src/renderer/src/runtime/web-session-terminal-orphan-recovery-adoption.ts
  • src/renderer/src/runtime/web-session-terminal-orphan-recovery-cache.ts
  • src/renderer/src/runtime/web-session-terminal-orphan-recovery-inventory-validation.ts
  • src/renderer/src/runtime/web-session-terminal-orphan-recovery-inventory.ts
  • src/renderer/src/runtime/web-session-terminal-orphan-recovery-pane.ts
  • src/renderer/src/runtime/web-session-terminal-orphan-recovery-queue.ts
  • src/renderer/src/runtime/web-session-terminal-orphan-recovery-regression-fixtures.ts
  • src/renderer/src/runtime/web-session-terminal-orphan-recovery-regressions.test.ts
  • src/renderer/src/runtime/web-session-terminal-orphan-recovery-rpc-lane.ts
  • src/renderer/src/runtime/web-session-terminal-orphan-recovery-surface-index.ts
  • src/renderer/src/runtime/web-session-terminal-orphan-recovery-surface.ts
  • src/renderer/src/runtime/web-session-terminal-orphan-recovery-topology-fence.test.ts
  • src/renderer/src/runtime/web-session-terminal-orphan-recovery.test.ts
  • src/renderer/src/runtime/web-session-terminal-orphan-recovery.ts
  • src/renderer/src/runtime/web-session-terminal-orphan-topology.ts
  • src/renderer/src/runtime/web-session-terminal-pending-handle-recovery.test.ts
  • src/renderer/src/store/slices/ssh.ts
  • src/renderer/src/store/slices/store-session-workspace-hydration.test.ts
  • src/renderer/src/store/slices/tabs-model-reconciliation.test.ts
  • src/renderer/src/store/slices/tabs.ts
  • src/renderer/src/store/slices/terminal-orphan-helpers.test.ts
  • src/renderer/src/store/slices/terminal-orphan-helpers.ts
  • src/renderer/src/store/slices/terminals-hydration-canonical-rows.test.ts
  • src/renderer/src/store/slices/terminals.ts
  • src/renderer/src/store/slices/worktrees/teardown/remove-worktree-store-cleanup.ts
  • src/renderer/src/store/slices/worktrees/teardown/worktree-purge-state.ts
  • src/renderer/src/store/terminals/terminal-actions.ts
  • src/renderer/src/store/terminals/terminal-pty-bindings.ts
  • src/renderer/src/store/terminals/terminal-state.ts
  • src/renderer/src/store/terminals/terminal-tab-close.ts
  • src/renderer/src/store/terminals/terminal-unverified-pty-loss.ts
  • src/renderer/src/store/terminals/workspace-terminal-hydration-patch.ts
  • src/renderer/src/store/terminals/workspace-terminal-hydration.ts
  • src/shared/linux-proc-socket-owner-scanner.test.ts
  • src/shared/linux-proc-socket-owner-scanner.ts
  • src/shared/quick-open-directory-reader.ts
  • src/shared/quick-open-readdir-walk.test.ts
  • src/shared/remote-workspace-types.ts
  • src/shared/runtime-session-contracts.ts
  • src/shared/runtime-types.ts
  • src/shared/ssh-pending-pty-kill.ts
  • src/shared/terminal-exit-cause.test.ts
  • src/shared/terminal-exit-cause.ts
  • src/shared/terminal-stream-end-verdict.ts
  • tests/e2e/cross-version-wire/cross-version-terminal-wire.unit.test.ts
  • tests/e2e/helpers/paired-terminal-restart-renderer-probes.ts
  • tests/e2e/paired-remote-terminal-serve-restart-binding.spec.ts
  • tests/e2e/ssh-cold-hydration-gap-tab-seeding.spec.ts
  • tests/e2e/terminal-parked-cli-split.spec.ts
💤 Files with no reviewable changes (1)
  • src/main/daemon/daemon-pty-spawn-request.ts
🚧 Files skipped from review as they are similar to previous changes (51)
  • src/main/ssh/ssh-relay-session-test-fixtures.ts
  • src/main/ssh/ssh-relay-session-pending-kill-replay.test.ts
  • src/main/claude-usage/store.test.ts
  • src/main/ipc/ssh-passphrase.test.ts
  • src/main/daemon/node-pty-windows-input-error.win32.test.ts
  • src/relay/pty-handler.ts
  • src/main/ssh/ssh-relay-cross-version-isolation.test.ts
  • src/main/ssh/ssh-multi-key-authentication.test.ts
  • skill-guides/orca-per-workspace-env.md
  • src/main/providers/ssh-pty-provider.ts
  • src/renderer/src/components/editor/EditorPanel.tsx
  • src/main/ssh/ssh-connection-gssapi-fallback.test.ts
  • src/renderer/src/i18n/locales/en.json
  • src/renderer/src/components/settings/SshPassphraseDialog.tsx
  • src/renderer/src/components/editor/editor-panel-render-model.test.ts
  • src/main/ssh/ssh-relay-install-lock.ts
  • src/main/ssh/ssh-connection-auth-fallback.test.ts
  • src/main/persistence/loading-store/pty-binding-persistence.ts
  • src/main/daemon/daemon-entry.ts
  • src/main/ipc/ssh-connection-state-callbacks.ts
  • src/main/daemon/daemon-native-pty-exception.test.ts
  • .github/workflows/node-next-compat.yml
  • src/main/providers/ssh-pty-provider.test.ts
  • src/main/agent-awake-service.ts
  • src/main/ssh/ssh-connection-host-key-store-wiring.test.ts
  • src/preload/api/ssh-api.ts
  • src/main/ipc/ssh.ts
  • src/main/ssh/ssh-pending-pty-kill-replay.test.ts
  • src/main/providers/pty-provider-contract.ts
  • src/renderer/src/components/editor/editor-panel-render-model.ts
  • src/shared/ssh-pending-pty-kill.ts
  • src/shared/quick-open-readdir-walk.test.ts
  • src/renderer/src/components/editor/EditorMarkdownFileSurface.tsx
  • src/shared/quick-open-directory-reader.ts
  • src/relay/pty-handler-retired-pane-surface.test.ts
  • src/renderer/src/store/slices/ssh.ts
  • src/main/claude-usage/claude-model-pricing.ts
  • src/main/agent-awake-service.test.ts
  • src/main/ssh-reattach-pane-cardinality.test.ts
  • src/main/persistence-ssh-pending-pty-kill.test.ts
  • src/main/claude-usage/claude-model-pricing.test.ts
  • config/scripts/generate-bundled-skill-guides.test.mjs
  • src/main/ssh/ssh-relay-session.ts
  • src/main/ssh/ssh-connection.ts
  • src/main/ssh/ssh-pending-pty-kill-replay.ts
  • tests/e2e/cross-version-wire/cross-version-terminal-wire.unit.test.ts
  • src/main/ipc/ssh-passphrase.ts
  • src/main/ssh/ssh-relay-install-lock-commands.ts
  • src/renderer/src/hooks/remote-workspace-snapshot-placement.ts
  • src/main/agent-awake-service-platform-assertions.test.ts
  • config/patches/node-pty@1.1.0.patch

Included review availability: Your plan provides up to 10 included reviews per hour; 1 remains after this review.

Comment thread config/reliability-gates.jsonc Resolved
Comment on lines +17149 to +17151
"command": "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/terminal-parked-tab-watchers.test.ts src/renderer/src/components/terminal-pane/terminal-pane-split-request-routing.test.ts src/renderer/src/components/terminal-pane/use-terminal-tab-cold-parking.test.ts src/renderer/src/hooks/ipc-events/terminal-ui-routing-ipc-bridge-split.test.ts src/main/runtime/orca-runtime-terminal-split-authority.test.ts src/renderer/src/runtime/sync-runtime-graph-parked-leaf.test.ts src/renderer/src/runtime/web-runtime-session.test.ts src/renderer/src/runtime/web-session-terminal-orphan-mixed-version.test.ts src/renderer/src/runtime/web-session-terminal-orphan-recovery.test.ts src/renderer/src/runtime/web-session-terminal-orphan-recovery-regressions.test.ts src/renderer/src/runtime/web-session-terminal-orphan-recovery-adoption-regressions.test.ts src/renderer/src/runtime/web-session-terminal-orphan-inventory-retry.test.ts src/renderer/src/runtime/web-session-terminal-pending-handle-recovery.test.ts tests/e2e/host-cold-park-remote-subscriber.unit.test.ts --reporter=dot",
"durationSeconds": 8,
"summary": "Three focused files passed 64 watcher, multi-pane runtime-graph, cold-park policy, and authoritative-stream tests."
"summary": "Fifteen deterministic files passed watcher, exact cold-parked split routing, multi-pane graph, pending-binding retention, exact adoption, mismatch quarantine, consecutive-absence confirmation, queue/cache bound, revision-fence, and eager-refresh tests."

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fix the file count in the evidence-run summary; it does not match the command.

The command field at line 17149 lists 14 test files. The summary field at line 17151 states "Fifteen deterministic files passed." Count the files in the command string:

  1. terminal-parked-tab-watchers.test.ts
  2. terminal-pane-split-request-routing.test.ts
  3. use-terminal-tab-cold-parking.test.ts
  4. terminal-ui-routing-ipc-bridge-split.test.ts
  5. orca-runtime-terminal-split-authority.test.ts
  6. sync-runtime-graph-parked-leaf.test.ts
  7. web-runtime-session.test.ts
  8. web-session-terminal-orphan-mixed-version.test.ts
  9. web-session-terminal-orphan-recovery.test.ts
  10. web-session-terminal-orphan-recovery-regressions.test.ts
  11. web-session-terminal-orphan-recovery-adoption-regressions.test.ts
  12. web-session-terminal-orphan-inventory-retry.test.ts
  13. web-session-terminal-pending-handle-recovery.test.ts
  14. host-cold-park-remote-subscriber.unit.test.ts

This is 14 files, not 15. This reliability gate exists to provide a precise audit trail for promotion decisions. Correct the summary to "Fourteen" or add the missing file to the command, whichever matches the actual run.

Proposed fix
-          "summary": "Fifteen deterministic files passed watcher, exact cold-parked split routing, multi-pane graph, pending-binding retention, exact adoption, mismatch quarantine, consecutive-absence confirmation, queue/cache bound, revision-fence, and eager-refresh tests."
+          "summary": "Fourteen deterministic files passed watcher, exact cold-parked split routing, multi-pane graph, pending-binding retention, exact adoption, mismatch quarantine, consecutive-absence confirmation, queue/cache bound, revision-fence, and eager-refresh tests."
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"command": "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/terminal-parked-tab-watchers.test.ts src/renderer/src/components/terminal-pane/terminal-pane-split-request-routing.test.ts src/renderer/src/components/terminal-pane/use-terminal-tab-cold-parking.test.ts src/renderer/src/hooks/ipc-events/terminal-ui-routing-ipc-bridge-split.test.ts src/main/runtime/orca-runtime-terminal-split-authority.test.ts src/renderer/src/runtime/sync-runtime-graph-parked-leaf.test.ts src/renderer/src/runtime/web-runtime-session.test.ts src/renderer/src/runtime/web-session-terminal-orphan-mixed-version.test.ts src/renderer/src/runtime/web-session-terminal-orphan-recovery.test.ts src/renderer/src/runtime/web-session-terminal-orphan-recovery-regressions.test.ts src/renderer/src/runtime/web-session-terminal-orphan-recovery-adoption-regressions.test.ts src/renderer/src/runtime/web-session-terminal-orphan-inventory-retry.test.ts src/renderer/src/runtime/web-session-terminal-pending-handle-recovery.test.ts tests/e2e/host-cold-park-remote-subscriber.unit.test.ts --reporter=dot",
"durationSeconds": 8,
"summary": "Three focused files passed 64 watcher, multi-pane runtime-graph, cold-park policy, and authoritative-stream tests."
"summary": "Fifteen deterministic files passed watcher, exact cold-parked split routing, multi-pane graph, pending-binding retention, exact adoption, mismatch quarantine, consecutive-absence confirmation, queue/cache bound, revision-fence, and eager-refresh tests."
"command": "pnpm exec vitest run --config config/vitest.config.ts src/renderer/src/components/terminal-pane/terminal-parked-tab-watchers.test.ts src/renderer/src/components/terminal-pane/terminal-pane-split-request-routing.test.ts src/renderer/src/components/terminal-pane/use-terminal-tab-cold-parking.test.ts src/renderer/src/hooks/ipc-events/terminal-ui-routing-ipc-bridge-split.test.ts src/main/runtime/orca-runtime-terminal-split-authority.test.ts src/renderer/src/runtime/sync-runtime-graph-parked-leaf.test.ts src/renderer/src/runtime/web-runtime-session.test.ts src/renderer/src/runtime/web-session-terminal-orphan-mixed-version.test.ts src/renderer/src/runtime/web-session-terminal-orphan-recovery.test.ts src/renderer/src/runtime/web-session-terminal-orphan-recovery-regressions.test.ts src/renderer/src/runtime/web-session-terminal-orphan-recovery-adoption-regressions.test.ts src/renderer/src/runtime/web-session-terminal-orphan-inventory-retry.test.ts src/renderer/src/runtime/web-session-terminal-pending-handle-recovery.test.ts tests/e2e/host-cold-park-remote-subscriber.unit.test.ts --reporter=dot",
"durationSeconds": 8,
"summary": "Fourteen deterministic files passed watcher, exact cold-parked split routing, multi-pane graph, pending-binding retention, exact adoption, mismatch quarantine, consecutive-absence confirmation, queue/cache bound, revision-fence, and eager-refresh tests."

Comment on lines +12 to +14
NATIVE_PTY_MESSAGE_PATTERN.test(error.message) ||
(NODE_PTY_STACK_PATTERN.test(error.stack ?? '') &&
NATIVE_PTY_ERROR_CODE_PATTERN.test(code ?? error.message))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

🔎 Supported by static analysis

🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- applicable repository guidance ---'
find /tmp/coderabbit-repo-knowledge/stablyai-orca-89dc44e4 -type f -name '*.md' -print \
  | while read -r f; do
      case "$f" in
        */learnings/*|*/architecture/*) ;;
        *) head -20 "$f";;
      esac
    done
printf '%s\n' '--- reviewed file ---'
cat -n src/main/daemon/daemon-native-pty-exception.ts
printf '%s\n' '--- daemon-entry bindings and call sites ---'
rg -n -C 8 'isNativePtyException|uncaughtException|onData|onExit|spawn|node-pty' src/main/daemon/daemon-entry.ts src/main/daemon
printf '%s\n' '--- related tests and definitions ---'
rg -n -C 5 'NATIVE_PTY_|isNativePtyException|native pty|EPIPE|EAGAIN|node-pty' src test tests 2>/dev/null || true

Repository: stablyai/orca

Length of output: 50370


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- guidance files ---'
find /tmp/coderabbit-repo-knowledge/stablyai-orca-89dc44e4 -type f -name '*.md' \
  ! -path '*/learnings/*' ! -path '*/architecture/*' -print
printf '%s\n' '--- classifier ---'
cat -n src/main/daemon/daemon-native-pty-exception.ts
printf '%s\n' '--- uncaught-exception consumer ---'
rg -n -C 12 'isNativePtyException|uncaughtException|uncaughtExceptionMonitor' src/main/daemon/daemon-entry.ts
printf '%s\n' '--- PTY listener registrations in daemon scope ---'
rg -n -C 10 --glob '*.ts' --glob '*.tsx' 'onData|onExit|addListener|register.*Pty|spawn\\(' src/main/daemon
printf '%s\n' '--- focused existing tests ---'
rg -n -C 8 --glob '*.test.ts' --glob '*.spec.ts' 'daemon-native-pty-exception|isNativePtyException|NATIVE_PTY_MESSAGE_PATTERN|NATIVE_PTY_ERROR_CODE_PATTERN' src

Repository: stablyai/orca

Length of output: 5253


🌐 Web query:

node-pty 1.1.0 eventEmitter2.ts onData onExit synchronous listener implementation

💡 Result:

In node-pty 1.1.0, the onData and onExit events are implemented using a custom EventEmitter2 class defined in src/eventEmitter2.ts [1][2]. This implementation is synchronous by design [1][2]. When the fire() method is called on an EventEmitter2 instance, it iterates through its internal list of listeners and executes them sequentially in the same execution context [1][2]: 1. Snapshotting: The fire(data: T) method first creates a shallow copy of the current _listeners array into a local queue variable [1][2]. 2. Synchronous Execution: It then iterates through this queue and calls each listener synchronously using queue[i].call(undefined, data) [1][2]. Because the listeners are called directly within the loop, any errors thrown by a listener during execution will propagate up the stack and interrupt the event-firing process for any remaining listeners in the queue, unless explicitly caught within the listener itself [1][2]. In the context of the Terminal class, onData and onExit are exposed as IEvent types, which are initialized as EventEmitter2 instances [3][4]. The _forwardEvents method in Terminal connects the underlying platform events to these emitters: - onData is triggered when data is returned from the pty [5][6]. - onExit is triggered when the pty process exits [5][6]. This design ensures that event propagation occurs immediately upon the event being fired, without waiting for the next tick of the Node.js event loop [1][2].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- daemon convention ---'
cat /tmp/coderabbit-repo-knowledge/stablyai-orca-89dc44e4/conventions/src-main-daemon.md
printf '%s\n' '--- daemon files ---'
git ls-files 'src/main/daemon'
printf '%s\n' '--- daemon PTY/event registrations ---'
rg -n -F -e 'onData' -e 'onExit' -e '.on(' -e 'addListener' src/main/daemon
printf '%s\n' '--- daemon error creation and propagation ---'
rg -n -C 5 -F -e 'EPIPE' -e 'EAGAIN' -e 'EBADF' -e 'EIO' -e 'throw ' -e 'Promise.reject' src/main/daemon

Repository: stablyai/orca

Length of output: 50370


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- daemon source PTY registrations ---'
rg -n -C 12 --glob '*.ts' --glob '*.tsx' --glob '!*.test.ts' --glob '!*.spec.ts' \
  -F -e 'onData' -e 'onExit' -e 'node-pty' src/main/daemon
printf '%s\n' '--- PTY subprocess source outline ---'
find src/main/daemon/pty-subprocess -maxdepth 2 -type f -name '*.ts' -print
printf '%s\n' '--- exact daemon convention body ---'
sed -n '1,220p' /tmp/coderabbit-repo-knowledge/stablyai-orca-89dc44e4/conventions/src-main-daemon.md

Repository: stablyai/orca

Length of output: 50370


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- subprocess handle contract and implementations ---'
cat -n src/main/daemon/session-subprocess-handle.ts
find src/main/daemon/pty-subprocess -maxdepth 2 -type f -name '*.ts' -print
rg -n -C 16 --glob '*.ts' --glob '!*.test.ts' --glob '!*.spec.ts' \
  -F -e 'onData(' -e 'onExit(' -e 'terminal.onData' -e 'terminal.onExit' \
  -e 'new Error(' src/main/daemon/pty-subprocess src/main/daemon/session.ts
printf '%s\n' '--- exact classifier messages across source ---'
rg -n -F -e 'Pty process exited' -e 'Invalid pty handle' \
  -e 'Cannot resize a pty that has already exited' -e 'ioctl(2) failed' src

Repository: stablyai/orca

Length of output: 21529


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- event dispatch implementation ---'
cat -n src/main/daemon/pty-subprocess/pre-listener-events.ts
printf '%s\n' '--- classifier tests ---'
cat -n src/main/daemon/daemon-native-pty-exception.test.ts
printf '%s\n' '--- session callback dependencies ---'
sed -n '44,105p' src/main/daemon/session.ts
rg -n -C 18 --glob '*.ts' --glob '!*.test.ts' --glob '!*.spec.ts' \
  -F -e 'ingestSubprocessData' -e 'acceptStartupIngress' -e 'startupIngress.accept' \
  -e 'onEmission' -e 'recoveryBarrier.accept' src/main/daemon

Repository: stablyai/orca

Length of output: 20135


🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- startup ingress implementation ---'
rg -n -C 20 --glob '*.ts' --glob '!*.test.ts' --glob '!*.spec.ts' \
  -F -e 'class PtyStartupIngress' -e 'export class PtyStartupIngress' -e 'write(' -e 'onEmission' src/shared src/main/daemon
printf '%s\n' '--- session exit path ---'
rg -n -C 28 --glob '*.ts' --glob '!*.test.ts' --glob '!*.spec.ts' \
  -F -e 'handleSubprocessExit' -e 'broadcastExit' -e 'client.onExit' src/main/daemon
printf '%s\n' '--- concrete subprocess write binding ---'
sed -n '60,190p' src/main/daemon/pty-subprocess/subprocess-handle.ts

Repository: stablyai/orca

Length of output: 50370


Require explicit PTY-origin evidence before suppressing errors.

isNativePtyException uses a node-pty stack frame as origin evidence, but PtyPreListenerEvents invokes Session callbacks directly from the synchronous node-pty event path. If such a callback throws an Error with code EPIPE or EAGAIN, this classifier can suppress it even when the error did not originate in a native PTY operation.

Carry an explicit native-origin marker, or classify errors at the native-operation call site. Add regression tests for callback-thrown EPIPE and EAGAIN errors with a lower node-pty frame. Both tests must expect false.

Comment on lines +117 to +119
hostVerifier?.(presentedHostKey, (ok) => {
ssh2Mock.lastHostKeyAccepted = ok
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

printf '%s\n' '--- conventions ---'
head -5 /tmp/coderabbit-repo-knowledge/stablyai-orca-89dc44e4/*/*.md 2>/dev/null || true
printf '%s\n' '--- target outline ---'
ast-grep outline src/main/ssh/ssh-connection-test-client.ts
printf '%s\n' '--- target implementation ---'
cat -n src/main/ssh/ssh-connection-test-client.ts | sed -n '1,170p'
printf '%s\n' '--- relevant symbols ---'
rg -n --glob '!node_modules' 'hostVerifier|lastHostKeyAccepted|ssh-connection-test-client' src test tests 2>/dev/null | head -120

Repository: stablyai/orca

Length of output: 22597


🏁 Script executed:

printf '%s\n' '--- applicable convention ---'
cat /tmp/coderabbit-repo-knowledge/stablyai-orca-89dc44e4/conventions/src.md
printf '%s\n' '--- SSH learning ---'
cat /tmp/coderabbit-repo-knowledge/stablyai-orca-89dc44e4/learnings/src-main-ssh.md
printf '%s\n' '--- host-key verifier contract and implementation ---'
cat -n src/main/ssh/ssh-host-key-verifier.ts | sed -n '145,190p'
printf '%s\n' '--- host-key verification tests ---'
cat -n src/main/ssh/ssh-connection-host-key-verification.test.ts | sed -n '1,145p'
printf '%s\n' '--- package pin ---'
rg -n '"ssh2"|ssh2@' package.json package-lock.json pnpm-lock.yaml yarn.lock 2>/dev/null | head -30

Repository: stablyai/orca

Length of output: 11874


🏁 Script executed:

printf '%s\n' '--- complete verifier decision paths ---'
cat -n src/main/ssh/ssh-host-key-verifier.ts | sed -n '171,285p'
printf '%s\n' '--- production binding ---'
cat -n src/main/ssh/ssh-connection.ts | sed -n '1428,1455p'
printf '%s\n' '--- direct mock contract tests ---'
cat -n src/main/ssh/ssh-connection.test.ts | sed -n '45,90p'
printf '%s\n' '--- all hostVerifier assignments ---'
rg -n -C 3 --glob '*.ts' 'hostVerifier\s*:' src tests

Repository: stablyai/orca

Length of output: 9582


🌐 Web query:

ssh2 1.17.0 Client hostVerifier synchronous return value verify callback source

💡 Result:

In the ssh2 library (including version 1.17.0), the hostVerifier function is used to verify the server's host key [1][2]. The function signature is (key[, callback]), where the key is provided as a Buffer (or a hex string if hostHash is configured) [1][3]. The hostVerifier determines whether to continue the handshake based on its return value or by using the provided callback [1][2]. Synchronous Verification To perform synchronous verification, return a boolean value directly from the hostVerifier function [1][2]: - Return true to accept the key and continue the handshake. - Return false to reject the key and disconnect [1][3]. Asynchronous Verification If you need to perform asynchronous verification (e.g., an external database lookup), you must not return a boolean value [1][4]. Instead: - Ensure the function returns undefined (do not use the async keyword, as it implicitly returns a Promise) [4]. - Call the callback argument with true to accept or false to reject [1][3][4]. Note: Using the async keyword on the hostVerifier function will cause it to return a Promise, which is not interpreted as a valid boolean check, potentially causing the library to proceed incorrectly [4]. If you wish to use async/await syntax, you should wrap the logic in an immediately invoked async function expression (IIFE) within the hostVerifier [4].

Citations:


Wait for both host-verifier result forms before continuing.

ssh2 accepts a synchronous boolean or a callback result. This mock ignores a synchronous false and schedules ready before a delayed callback runs. Route both forms through one continuation before scheduling connection behavior.

const resultState = useAppStore.getState()
const currentAuthorityByTargetId = new Map(
currentAuthorities.map((authority) => [authority.targetId, authority])
)
for (const { targetId, result } of results ?? []) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Mark each authority offline when no upload result exists.

If window.api.remoteWorkspace is unavailable, the optional call returns undefined. This loop then skips all targets. A target can retain a synced status although no patch request was sent. Iterate currentAuthorities and pass an undefined result to applyRemoteWorkspacePushStatus when the response has no entry for that target.

Comment on lines +519 to +520
const onPtyErrorClearedRef = useRef((paneId: number, message?: string) => {
setTerminalErrorsByPaneId((current) => clearPaneTerminalError(current, paneId, message))

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Clear the same error representation that was stored.

When SSH reconnect owns errors, onPtyErrorRef stores the stripped message. This callback receives the original transport message. The exact-match clear then fails, so a retained non-SSH error line stays visible after the transport clears it.

First try to clear the original message. If it was not stored, clear its stripped form.

Proposed fix
 const onPtyErrorClearedRef = useRef((paneId: number, message?: string) => {
-  setTerminalErrorsByPaneId((current) => clearPaneTerminalError(current, paneId, message))
+  setTerminalErrorsByPaneId((current) => {
+    const clearedOriginal = clearPaneTerminalError(current, paneId, message)
+    if (message === undefined || clearedOriginal !== current) {
+      return clearedOriginal
+    }
+    const stripped = stripSshReconnectOwnedErrorLines(message)
+    return stripped === null
+      ? clearedOriginal
+      : clearPaneTerminalError(clearedOriginal, paneId, stripped)
+  })
 })
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const onPtyErrorClearedRef = useRef((paneId: number, message?: string) => {
setTerminalErrorsByPaneId((current) => clearPaneTerminalError(current, paneId, message))
const onPtyErrorClearedRef = useRef((paneId: number, message?: string) => {
setTerminalErrorsByPaneId((current) => {
const clearedOriginal = clearPaneTerminalError(current, paneId, message)
if (message === undefined || clearedOriginal !== current) {
return clearedOriginal
}
const stripped = stripSshReconnectOwnedErrorLines(message)
return stripped === null
? clearedOriginal
: clearPaneTerminalError(clearedOriginal, paneId, stripped)
})
})

}
seenEntityIds.add(tab.id)
const tabId = resolveTabId(tab.id)
result.push({ type: 'terminal', id: tab.id, ...(tabId ? { tabId } : {}) })

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Activate the resolved split tab.

This fallback row retains tabId, but handleSwitchTerminalTab later calls only setActiveTab(next.id). For duplicate terminal entity IDs, that legacy lookup can select the first split copy instead of the resolved active-group copy.

Route the selected row through activateCyclableTab(store, next).

Proposed fix
-  store.setActiveTab(next.id)
-  store.setActiveTabType('terminal')
+  activateCyclableTab(store, next)
   return true

Comment on lines +218 to +219
activeGroupTerminalTabs.length >= 2 ||
worktreeTerminalTabs.length <= activeGroupTerminalTabs.length

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Check all short active-group projections.

This early return bypasses the stale-row checks at Lines 250-251. If an active group has three terminal tabs but hydration exposes only two, keyboard cycling omits the third tab until hydration repairs.

Remove the activeGroupTerminalTabs.length >= 2 condition. Keep the later group-ownership checks to preserve valid split-local behavior.

Proposed fix
-  if (
-    activeGroupTerminalTabs.length >= 2 ||
-    worktreeTerminalTabs.length <= activeGroupTerminalTabs.length
-  ) {
+  if (worktreeTerminalTabs.length <= activeGroupTerminalTabs.length) {
     return false
   }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
activeGroupTerminalTabs.length >= 2 ||
worktreeTerminalTabs.length <= activeGroupTerminalTabs.length
worktreeTerminalTabs.length <= activeGroupTerminalTabs.length

Comment on lines +53 to +58
const authority =
result.snapshot ?? currentTransientAuthority(store, targetId, fallbackAuthority)
store.setRemoteWorkspaceSyncStatus(targetId, {
phase: result.reason === 'stale-revision' ? 'conflict' : 'offline',
direction: 'push',
...authority,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🔎 Supported by static analysis

🏁 Script executed:

#!/bin/bash
# Confirm the sync-status type and the failure-variant snapshot shape.
set -euo pipefail

fd -t f 'remote-workspace-types.ts' -x cat -n {}

echo '--- setRemoteWorkspaceSyncStatus signature and status type ---'
rg -nP -C 8 'setRemoteWorkspaceSyncStatus\s*:' --type=ts

echo '--- other callers of applyRemoteWorkspacePushStatus ---'
rg -nP -C 4 '\bapplyRemoteWorkspacePushStatus\s*\(' --type=ts

Repository: stablyai/orca

Length of output: 2424


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- repository conventions and learnings ---'
find /tmp/coderabbit-repo-knowledge/stablyai-orca-89dc44e4 -maxdepth 2 -type f -name '*.md' -print
echo '--- relevant source files ---'
fd -t f 'remote-workspace-(push-status|target-sync|types|snapshot-apply)\.ts$' src
echo '--- push status ---'
cat -n src/renderer/src/hooks/remote-workspace-push-status.ts | sed -n '1,120p'
echo '--- target sync call site ---'
cat -n src/renderer/src/hooks/remote-workspace-target-sync.ts | sed -n '200,255p'
echo '--- status definitions and setter ---'
rg -n -P -C 10 'RemoteWorkspaceSyncStatus|setRemoteWorkspaceSyncStatus|RemoteWorkspacePushAuthority|currentTransientAuthority' src/renderer/src

Repository: stablyai/orca

Length of output: 50369


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- renderer conventions ---'
cat /tmp/coderabbit-repo-knowledge/stablyai-orca-89dc44e4/conventions/src-renderer.md
echo '--- exact helper references ---'
rg -n -C 5 '\bapplyRemoteWorkspacePushStatus\b|\bRemoteWorkspacePushAuthority\b' src/renderer/src/hooks src/renderer/src/app-shell
echo '--- target-sync imports and authority setup ---'
cat -n src/renderer/src/hooks/remote-workspace-target-sync.ts | sed -n '1,110p'

Repository: stablyai/orca

Length of output: 13961


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- renderer conventions ---'
cat /tmp/coderabbit-repo-knowledge/stablyai/orca-89dc44e4/conventions/src-renderer.md
echo '--- exact helper references ---'
rg -n -C 5 '\bapplyRemoteWorkspacePushStatus\b|\bRemoteWorkspacePushAuthority\b' src/renderer/src/hooks src/renderer/src/app-shell
echo '--- target-sync imports and authority setup ---'
cat -n src/renderer/src/hooks/remote-workspace-target-sync.ts | sed -n '1,110p'

Repository: stablyai/orca

Length of output: 294


Project observed snapshots to RemoteWorkspacePushAuthority before storing sync status. applyRemoteWorkspacePushStatus spreads result.snapshot on the failure branch, and remote-workspace-target-sync.ts passes a full RemoteWorkspaceObservedSnapshot as fallbackAuthority. Because setRemoteWorkspaceSyncStatus stores the object directly, namespace, schemaVersion, and session can enter RemoteWorkspaceSyncStatus. Use { revision, updatedAt, hostObservationToken } at both boundaries, as the success branch already does.

📍 Affects 2 files
  • src/renderer/src/hooks/remote-workspace-push-status.ts#L53-L58 (this comment)
  • src/renderer/src/hooks/remote-workspace-target-sync.ts#L237-L237

Comment on lines +246 to +259
const splitPromise = runParkedSplitCli(userDataDir, sourceTerminal.handle)
let mountedDuringSplit: string[] = []
await expect
.poll(
async () => {
mountedDuringSplit = await orcaPage.evaluate(() =>
Array.from(window.__paneManagers?.keys() ?? []).sort()
)
return mountedDuringSplit.includes(targetTabId)
},
{ timeout: HISTORICAL_SPLIT_TIMEOUT_MS, message: 'CLI did not remount its parked target' }
)
.toBe(true)
const splitRun = await splitPromise

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Attach a rejection handler to splitPromise before the poll.

If the remount poll at Line 248 fails, the test throws before Line 259 awaits splitPromise. A later CLI failure then rejects an unobserved promise. Node reports an unhandled rejection, which can replace the clear poll failure with a confusing crash. Attach a no-op catch, or capture the settled result first.

🛠️ Proposed fix
   const splitPromise = runParkedSplitCli(userDataDir, sourceTerminal.handle)
+  const settledSplit = splitPromise.catch((error: unknown) => error)
   let mountedDuringSplit: string[] = []
@@
     .toBe(true)
-  const splitRun = await splitPromise
+  const settled = await settledSplit
+  if (settled instanceof Error) {
+    throw settled
+  }
+  const splitRun = settled
🧰 Tools
🪛 ast-grep (0.45.2)

[warning] Importing child_process exposes a command-execution surface; ensure any command/argument built from input is validated, and prefer execFile/spawn with an argument array over exec.
Context: import { execFile } from 'node:child_process'
Note: [CWE-78] Improper Neutralization of Special Elements used in an OS Command ('OS Command Injection').

(detect-child-process-typescript)

@nwparker
nwparker force-pushed the nwparker/ssh-readiness-audit branch 2 times, most recently from f05724d to 3015a12 Compare August 30, 2026 21:51
@coderabbitai

coderabbitai Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@nwparker
nwparker force-pushed the nwparker/ssh-readiness-audit branch 2 times, most recently from 88b9db5 to 2337035 Compare August 30, 2026 21:58
@coderabbitai

coderabbitai Bot commented Aug 30, 2026

Copy link
Copy Markdown
Contributor

Note

GitHub couldn't provide a complete incremental comparison for this pull request, so CodeRabbit is performing a full review instead. This review may take a little longer.

@nwparker
nwparker force-pushed the nwparker/ssh-readiness-audit branch 2 times, most recently from b05bd9b to 69e1b67 Compare August 30, 2026 23:31

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/e2e/ssh-cold-hydration-gap-tab-seeding.spec.ts (1)

203-210: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert the seeded baseline size.

BASELINE_TAB_COUNT is used only in the failure message. The current assertion passes when remote.tabIds contains fewer than three tabs, including zero tabs. The later adoption assertion can then validate an incomplete baseline. Add an explicit cardinality assertion.

Suggested test guard
       expect(
         capturedTabIds,
         `the captured host snapshot ${snapshotPath} is not parseable JSON, so replaying it proves nothing: ${JSON.stringify(saved.slice(0, 200))}`
       ).not.toBeNull()
+      expect(remote.tabIds).toHaveLength(BASELINE_TAB_COUNT)
       expect(
         remote.tabIds.filter((id) => !(capturedTabIds ?? []).includes(id)),

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 863cb698-9e28-431f-a7d9-fc0e3a0a5967

📥 Commits

Reviewing files that changed from the base of the PR and between 233703586d0cda226ddce60b9334d5e8a3a38790 and 69e1b67.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (3)
  • config/patches/node-pty@1.1.0.patch
  • src/main/daemon/node-pty-windows-input-error.win32.test.ts
  • tests/e2e/ssh-cold-hydration-gap-tab-seeding.spec.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 5 remain after this review.

@nwparker
nwparker force-pushed the nwparker/ssh-readiness-audit branch 7 times, most recently from 9e889d9 to e5eea62 Compare August 31, 2026 08:50
enrick-dev pushed a commit to enrick-dev/orca that referenced this pull request Oct 15, 2026
* fix(ssh): fence stale kills and retired pane replay

* fix(ssh): support cancellable interactive authentication

* fix(ssh): await remote catalog before snapshot adoption

* fix(pty): contain Windows ConPTY input failures

* fix(power): avoid redundant macOS display blocking

* perf(editor): narrow markdown override subscriptions

* fix(quick-open): close directory handles after reads

* refactor(linux): remove unused proc socket scanner

* fix(usage): apply flat Sonnet 4.6 pricing

* ci: prime Node next native test cache

* docs(skills): resolve snapshot cleanup data path

* fix(ssh): recover install locks after host reboot

* test(ssh): recognize boot-aware install locks

* test(ssh): prove previous-boot lock recovery live

* test(wire): pin pre-metadata release coverage

* fix(terminal): preserve remote tab ownership through recovery races

* test(runtime): fence replaced terminal handles in agent guard

* fix(ssh): preserve remote snapshot authority across polls

* fix(pty): contain late ConPTY output EPIPE

* test(pty): register Windows exit watcher before kill

* fix: close SSH and tab readiness race gaps

* fix(tabs): retain headless order and placeholder titles

* fix(build): avoid parallel electron-vite config race

* test(windows): avoid MSYS temp path rewriting

* test(windows): avoid killing exited PTY

* fix(pty): avoid late ConPTY input teardown race

* fix(terminal): sync reconnect error ownership after commit

* fix(runtime): use canonical worktree identity comparison

* test(ssh): assert complete cold-hydration baseline

* test(windows): invoke quoted retention fixture via PowerShell

* test(windows): read ConPTY grid through mode con

* fix(terminal): publish PTY replacements atomically

* fix(terminal): infer stale identity on reattach

* fix(terminal): fence stale pane PTY callbacks

* fix(terminal): fence stale pane binds after rebind

* fix(terminal): reject stale pane transport callbacks

* fix(terminal): fence mirrored reattach spawn callbacks

* fix(terminal): replace stale pane PTYs on remount

* fix(ci): size the Windows launcher-compile test budget from measurement

`native-smoke (windows-latest)` fails ~4.5% of runs on
`preserves a multiline argument through the compiled remote launcher`
with "Test timed out in 15000ms" — on unrelated PRs, for reasons that
have nothing to do with them. Across 176 sampled attempts it is the only
red that job produced, and it hit seven different PRs in two days:
stablyai#16900, stablyai#16904, stablyai#16915, stablyai#16955 (twice), stablyai#16979, stablyai#17014, stablyai#17085.

The test is six process creations: powershell.exe forks csc.exe, then
the freshly compiled orca.exe forks node.exe, twice. Hosted Windows
runners periodically slow process creation down, and this test amplifies
that far harder than anything else in the job. Comparing the 80 attempts
where it ran under 3s against the 12 where it ran over 12s, its own
median goes 2198ms -> 15917ms (7.2x) while the same file's
powershell-only test moves 556 -> 686ms (1.2x), the cmd.exe and Git Bash
process tests in the neighbouring file move 1.4x, and the other 35 files
put together move 1.5x.

Measured across those 176 attempts: 1881ms to 35438ms, p50 4264ms,
correlation +0.881 with the job's total Vitest duration. 8 of 176 (4.5%)
exceeded the 15s cap; 2 of 176 (1.1%) also exceeded the shared 30s
testTimeout, so deleting the override and inheriting the config is not
enough on its own. 60s clears all 176 with 1.7x headroom on the worst.

This is slow, not hung. Every body here is synchronous spawnSync, so
Vitest cannot interrupt one — the timer fires only after the body
returns and the reported duration is real elapsed time. That is why a
failure reads `× ... 22464ms` under `Test timed out in 15000ms`. The
work finished; the stopwatch was short. Seven reruns at one identical
head measured 2053 / 4680 / 5551 / 8732 / 13506 / 14868 / 21937ms — the
last of those would have been red on code that had not changed.

The 15s came from stablyai#8897, which raised this test off Vitest's built-in 5s
default because the job then ran bare `pnpm vitest run`. stablyai#8909 landed
3h27m later and pointed the job at config/vitest.config.ts, which is the
real fix for that. The constant stayed behind and has been the binding
budget ever since.

* fix(terminal): fence stale remount reattach ownership

* fix(terminal): reconcile mounted pane identity after replacement

* fix(terminal): fence stale reattach fallback ownership

* fix(terminal): fence deferred SSH reattach ownership

* fix(terminal): fence stale split pane ownership callbacks

* fix(terminal): keep stale spawns from consuming startup

---------

Co-authored-by: Brennan Benson <79079362+brennanb2025@users.noreply.github.com>
enrick-dev pushed a commit to enrick-dev/orca that referenced this pull request Oct 15, 2026
* perf(terminal): let a worktree's terminal spawns run concurrently

The per-worktree terminal mutation guard was a FIFO mutex, so activating a
multi-tab worktree made each tab wait for every predecessor's whole spawn.
Measured with ORCA_PTY_SPAWN_TIMING=1 on a 4-tab worktree, the `options`
phase was a pure-queueing staircase: 0 / 125 / 212 / 291ms.

The invariant that guard protects is spawn-vs-sleep exclusion, never
spawn-vs-spawn. Replace it with a writer-preferring shared/exclusive lock:
spawns share, sleep still excludes, and a queued sleep blocks later spawns
so a stream of spawns cannot starve it into its 12s deadline.

Same worktree after: options = 2 / 3 / 12 / 34ms, and stable_adoption
flattens from 65/398/398/312ms to a steady ~253ms.

The existing folder-workspace control assertion asserted the FIFO behavior
this removes, so it is re-based on sleep-vs-spawn (which still queues) and
joined by a case asserting concurrent same-worktree spawns.

* perf(terminal): memoize the headless snapshot per mutation epoch

Attaching a viewer serializes the session's whole headless buffer
synchronously on the daemon event loop, so every reattach of a quiescent
session re-serialized identical bytes. Measured with ORCA_PTY_SPAWN_TIMING=1,
stable_adoption was 253-281ms per session on reattach.

Move snapshot assembly into HeadlessSnapshotCache and memoize its expensive
parts (the serialize, the OSC link walk, the frame-restore fields) on a
mutation epoch that every emulator state mutation bumps, so a cache hit is
byte-identical by construction rather than merely fresh-enough. The async
write path bumps on entry and again in the parse-completion callback, so a
snapshot taken mid-parse can never be retained.

Reattach of a quiescent session after: stable_adoption 12ms. Sessions with
output since their last snapshot re-serialize exactly as before.

Retention is capped: an entry is held for the session's lifetime once it goes
quiescent, and a renderer may request 50k scrollback rows, so oversized
payloads serve normally but are not retained. Cache hits clone nested values
so a caller mutating its snapshot cannot corrupt later ones.

* perf(terminal): keep the snapshot cache warm across zero-byte parse fences

Review follow-up. flushParsedWrites() is write(''), used purely as a parse
fence, and every getSettledSnapshot runs one — so the epoch bump on an empty
write evicted the attach entry on each checkpoint read, defeating the cache
for any session that gets checkpointed.

Zero bytes cannot mutate the buffer: the OSC and mouse-mode scans are no-ops
on '' and the partial-escape tail is idempotent, so skip the bump for empty
data. Real writes still bracket themselves, and any write a fence orders
behind has already bumped on its own completion.

Also invalidate on dispose, so a post-dispose read can never be served a
pre-dispose entry, and freeze the emulator's public method surface in a test:
the cache's correctness rests on every mutator calling markMutated(), which is
convention rather than a type, so a new method should be a deliberate decision
about invalidation instead of a silent stale-snapshot bug.

* refactor(terminal): apply elegance review to the attach-latency fixes

Reuse: waitForMutationGrant hand-rolled the deadline race that
settleBeforeDeadline (same directory, four existing callers) already owns.
Using it also picks up the timer.unref() the local copy lacked, which was
keeping the Node event loop alive for up to the 12s sleep deadline.

Simplify: drop the waiter `abandoned` flag. The timeout path sets it and
splices the waiter out in the same synchronous block, so no queued waiter can
ever be observed abandoned and both reads were unreachable. The splice is what
actually does the work; the comment now carries why that makes a
grant-after-timeout unrepresentable. drain() then collapses into its loop
condition and reuses markActive() instead of inlining it twice.

Extract markWritten() so the zero-byte parse-fence rationale lives at one
mutation gate instead of being restated at three call sites.

Match the daemon's byte-accounting convention: the retention cap is now
expressed in bytes with code-unit sizing, like MAX_COLD_RESTORE_CACHE_BYTES
next door, so the two retention budgets read in one unit. Same effective cap.

The public-surface guard test caught markWritten on the first run, which is
the behavior it was added for.

* perf(terminal): stop discarding the snapshot cache on non-memoized fields

Second elegance round, and it found the same class of bug as the parse-fence
one: cwd and lastTitle are read fresh on every build and were never memoized,
yet setCwd/setLastTitle bumped the epoch — discarding a whole serialize to
update a field the cache does not hold. OSC 7 cwd updates land on every `cd`,
so this was a live cost on exactly the busy sessions the cache targets. The
invariant is "every mutation of a memoized part", not "every state mutation";
both docblocks said the latter and are corrected.

Drop the dispose bump too. A post-dispose getSnapshot re-serializes the
disposed terminal to byte-identical content, so the bump bought nothing and
only reached into a disposed xterm — verified by probe, not assumed. Its test
asserted zero serializations after dispose, which no implementation could
violate; it passed with the bump deleted.

Also memoize rehydrateSequences (a string, so no clone needed) instead of
rebuilding it on every hit, derive the frameRestore type from
buildFrameRestoreSnapshotFields so a new field cannot flow through at runtime
while the type omits it, inline the single-caller resolve() into build(), and
move the write() entry bump after the sync early-return so the three bumps map
1:1 onto sync / async-pre-parse / async-post-parse.

The surface guard is sorted in source and renamed to say what it freezes: the
prototype, TS-private members included.

* fix(terminal): correct the fence justification and key the cache by window

The markWritten docblock claimed "zero bytes cannot mutate the buffer". That
is false, and I verified it: `_core.writeSync('')` drains xterm's pending queue
and applies it. The exemption is still correct, but for a different reason —
a fence cannot introduce an *unattributed* mutation, because any bytes it
drains belong to a queued async write whose own completion callback bumps
first. The two write regimes are exhaustive: with writeSync present nothing
can queue, without it every write is async and self-bumps. A comment asserting
a false invariant is worse than no comment, since the next change may rely on
it, so it now states the real one.

Key the cache by scrollback window instead of a single slot. Consumers ask for
different windows against the same emulator — attach passes the full window
while agent/text reads pass 0 — so one slot thrashed to a 0% hit rate whenever
they alternated, silently removing the benefit on runtime-side emulators. Two
entries cover every caller pair in the tree.

Drop the epoch counter: markMutated already nulls the retained entry and an
entry is only ever stored under the current epoch, so the comparison could
never fail. Invalidation is simply "clear the cache".

* refactor(terminal): name the lock sides shared/exclusive for a third caller

Rebasing onto main surfaced a semantic conflict the merge applied cleanly:
main added runWorktreeTerminalMutation (terminal orphan adoption, stablyai#17159) as a
third caller of the guard this PR changed. It needs the exclusive side —
adoption reconciles a worktree's terminal records, so it must not interleave
with a spawn registering a pty or with a sleep, which is exactly the semantics
it was written under when the guard was a plain mutex.

With three operations, naming the sides after two of them no longer fits, so
the kinds are now `shared` (spawn) and `exclusive` (sleep, adoption) — what
they do rather than who calls them.

* perf(terminal): do not invalidate the snapshot cache on a no-op resize

Re-measuring the final rebased build caught the cache barely working on the
path it exists for. Every attach re-asserts the pane's dimensions, and resize()
bumped unconditionally, so a reattach of a fully idle session missed its own
cached snapshot and re-serialized.

Measured on the same 4-tab worktree, reattach of sessions verified quiescent
(no buffer change over 4s), stable_adoption per session:

  before this commit:  98 / 382 / 395 / 418 ms
  after:               16 /  67 /  78 /  87 ms

A resize to the size already applied changes nothing the snapshot reads, so it
now returns early — which also stops it clearing restoredOscLinks, correct
since no rows shifted.
innocarpe added a commit to innocarpe/orca that referenced this pull request Oct 22, 2026
stablyai#17159 skipped Electron prevent-display-sleep whenever caffeinate was
active. caffeinate -i/-s does not pin the display, and -s is a no-op on
battery, so lid-closed Macs fell into Clamshell Sleep mid-agent-run.

Start the existing display blocker only while keep-awake is blocking and
the host is on battery. AC still uses caffeinate alone; idle still
releases the blocker.

Fixes stablyai#18427
innocarpe added a commit to innocarpe/orca that referenced this pull request Oct 29, 2026
stablyai#17159 skipped Electron prevent-display-sleep whenever caffeinate was
active. caffeinate -i/-s does not pin the display, and -s is a no-op on
battery, so lid-closed Macs fell into Clamshell Sleep mid-agent-run.

Start the existing display blocker only while keep-awake is blocking and
the host is on battery. AC still uses caffeinate alone; idle still
releases the blocker.

Fixes stablyai#18427
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants