You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix(orcad): a client that reconnects during idle shutdown can still open terminals - #29958
A server that Orca starts on an SSH host shuts itself down once nobody has used it for a while. Before it exits, it also shuts down its terminal helper, but only if that helper has no terminals open. If you reconnected during those last few moments, the server cancelled its shutdown and stayed connected, but its terminal helper was already gone and could never be started again. Every new terminal then failed with "Terminal daemon is decommissioning" until the server was restarted by hand. Now the server starts a fresh terminal helper when that happens, so the reconnected client can open terminals as usual.
What Changed
Before: you reconnect to a managed SSH host just as its idle shutdown begins. The server stays up and the client shows as connected, but opening a terminal fails with Terminal daemon is decommissioning, and keeps failing.
After: the server notices the client came back after the terminal daemon agreed to retire, starts a fresh daemon, and keeps serving. New terminals open right away.
Mechanism (new orcad-idle-stop-daemon-recovery.ts, wired in orcad-managed-idle-exit-host.ts):
Retirement is treated as a point of no return whenever the daemon agreed to retire, whether the agreement arrived on time, arrived late after the 5 s timeout (lateRetirement), or might have arrived but its reply was lost. In the lost-reply case the server asks the daemon again. The daemon's own check-and-fence decides atomically, so a client that started a terminal in the meantime makes the daemon refuse, and that daemon is kept.
A replacement happens only after the daemon itself agreed to retire. It uses the existing restartDaemon(), targeted at the pid of the daemon that retired (expectedDaemonPid). The ownership check is skipped only when the endpoint is proven dead (refused or missing), so a different live daemon at that endpoint is never stopped.
If no replacement is possible, the server finishes the idle stop, and the client's reconnect relaunches a working server. The exception is work an exit would end (a chat session, a running agent, an enabled automation, a staged migration, or a held activation fence). In that case the server stays up rather than end that work.
isDaemonTerminalAdmissionClosed() checks the router's fence as well as the current daemon's.
daemon-provider-restart.ts no longer carries an older daemon generation that also retired into the new provider, because its dead endpoint would make every terminal inventory fail.
persistOrcadDaemonRetirementRecord now writes only the record's own fields, so the in-memory late-retirement promise can never break the strict on-disk stop receipt.
Why
The review suggested two options: finish the shutdown and let the client relaunch a server, or swap in a fresh terminal provider. Finishing the shutdown would also end any chat or automation that started during the window, and the reconnecting client would wait through a full relaunch. Swapping the daemon reuses the restart sequence that the Settings "restart daemon" action already runs, and it keeps the connection. It is only allowed once the daemon has atomically agreed to retire, so it cannot end a live terminal. Finishing the shutdown remains as the fallback if the swap fails.
Linked Issue
From the third external Codex review of the SSH consolidation (finding 1, P1; introduced by #26473).
Visual Proof
N/A: this is a server lifecycle fix with no UI change. The live before/after transcript is under Testing.
Testing
I manually tested these changes locally
Automated tests added/updated, or explained why not below
Unit tests
orcad-managed-idle-exit-retired-reconnect.test.ts uses the real DaemonPtyRouter, router retirement, retireOrcadDaemonIfIdle and provider registry. The client returns while retirement is pending. Before the fix, the next spawn rejects with Terminal daemon is decommissioning; after it, the spawn succeeds. The suite also covers:
a lost reply that the second ask settles as retired (replaced) or busy (kept, and still serving);
a failed replacement, which finishes the stop, or stays up when the client started an automation;
a busy daemon, which is never replaced.
orcad-idle-stop-daemon-recovery.test.ts: a late retirement after a timeout, a late refusal, and an unanswered second ask (never replaced).
orcad-daemon-retirement.test.ts: the late verdict is reported only after admission has reopened.
daemon-init-restart-sequence.test.ts: a targeted restart proceeds over a proven-dead endpoint, still refuses when a probe is inconclusive or a different pid owns the endpoint, and drops a retired legacy generation.
daemon-router-retirement.test.ts: a pending router census counts as closed admission.
orcad-managed-stop.test.ts: a timed-out outcome still writes a valid stop receipt.
Live on omarchy (Linux x64), fully isolated: linux-x64 orcad builds of main and of this branch ran from /tmp, with their own HOME, XDG dirs and ORCA_USER_DATA, a free loopback port, ORCA_ORCAD_MANAGED_ACTIVATION_ROOT set and ORCA_E2E_ORCAD_IDLE_TIMEOUT_MS=4000. To open the reconnect window on demand, the script paused the daemon with SIGSTOP during the final idle check, connected a WebSocket client (an E2EE hello, which counts as an open connection), then resumed the daemon. Retirement then committed with the client present. After that, it ran orca terminal create through the host's local CLI.
main: terminal daemon on idle stop: retired → the server stayed up → terminal create failed with "message": "Terminal daemon is decommissioning".
this branch: retired → a client returned during idle stop; started a fresh terminal daemon → terminal create returned ok: true, and reading the terminal showed its LIVE-OK output. Once the terminal was closed and the client had left, the server idled out again normally: the fresh daemon retired and orcad exited.
Not verified live, unit tests only: the lost-reply and late-reply paths (they cannot be timed deterministically on a real host), the legacy-generation drop, and macOS/Windows hosts.
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 0 remain after this review.
📝 Walkthrough
The changes add daemon admission and PID queries, targeted restart checks for endpoints that may be gone, and explicit results for late retirement attempts. A new recovery flow uses retirement outcomes and admission state to decide whether to retain or replace the daemon. Managed idle exit now invokes that flow when activity returns during retirement and uses the recovery result and remaining work to decide whether to shut down.
Priority: ➖ Normal
Merge Risk:⚪ Minimal · up to e69ab
No identified issue blocks merging. Terminals remain available through the local fallback when the daemon provider is unavailable.
Pre-merge checks: 4 · 1
Failed checks: 1 warning
Check name
Status
Explanation
Resolution
Docstring Coverage
Docstring coverage is 47.06% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 17 functions across 18 files.
Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed checks: 4 passed
Check name
Status
Explanation
Title check
The title clearly and concisely describes the main fix: restoring terminal access when a client reconnects during idle shutdown.
Description check
The description is detailed and mostly complete. It explains the user-visible problem, mechanism, rationale, testing, live Linux verification, and known unverified platforms and paths. The Linked Issu…
Linked Issues check
Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check
Check skipped because no linked issues were found for this pull request.
Full details: Description check
Explanation
The description is detailed and mostly complete. It explains the user-visible problem, mechanism, rationale, testing, live Linux verification, and known unverified platforms and paths. The Linked Issue section references #26473 but does not provide an actual issue link or Fixes # entry.
Fix all pre-merge checks with AI
✨ Finishing Touches: 💡 1
📝 Generate docstrings: 💡
Commit to this branch
Create a new PR
Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
ELI5
A server that Orca starts on an SSH host shuts itself down once nobody has used it for a while. Before it exits, it also shuts down its terminal helper, but only if that helper has no terminals open. If you reconnected during those last few moments, the server cancelled its shutdown and stayed connected, but its terminal helper was already gone and could never be started again. Every new terminal then failed with "Terminal daemon is decommissioning" until the server was restarted by hand. Now the server starts a fresh terminal helper when that happens, so the reconnected client can open terminals as usual.
What Changed
Terminal daemon is decommissioning, and keeps failing.Mechanism (new
orcad-idle-stop-daemon-recovery.ts, wired inorcad-managed-idle-exit-host.ts):lateRetirement), or might have arrived but its reply was lost. In the lost-reply case the server asks the daemon again. The daemon's own check-and-fence decides atomically, so a client that started a terminal in the meantime makes the daemon refuse, and that daemon is kept.restartDaemon(), targeted at the pid of the daemon that retired (expectedDaemonPid). The ownership check is skipped only when the endpoint is proven dead (refusedormissing), so a different live daemon at that endpoint is never stopped.isDaemonTerminalAdmissionClosed()checks the router's fence as well as the current daemon's.daemon-provider-restart.tsno longer carries an older daemon generation that also retired into the new provider, because its dead endpoint would make every terminal inventory fail.persistOrcadDaemonRetirementRecordnow writes only the record's own fields, so the in-memory late-retirement promise can never break the strict on-disk stop receipt.Why
The review suggested two options: finish the shutdown and let the client relaunch a server, or swap in a fresh terminal provider. Finishing the shutdown would also end any chat or automation that started during the window, and the reconnecting client would wait through a full relaunch. Swapping the daemon reuses the restart sequence that the Settings "restart daemon" action already runs, and it keeps the connection. It is only allowed once the daemon has atomically agreed to retire, so it cannot end a live terminal. Finishing the shutdown remains as the fallback if the swap fails.
Linked Issue
From the third external Codex review of the SSH consolidation (finding 1, P1; introduced by #26473).
Visual Proof
N/A: this is a server lifecycle fix with no UI change. The live before/after transcript is under Testing.
Testing
Unit tests
orcad-managed-idle-exit-retired-reconnect.test.tsuses the realDaemonPtyRouter, router retirement,retireOrcadDaemonIfIdleand provider registry. The client returns while retirement is pending. Before the fix, the next spawn rejects withTerminal daemon is decommissioning; after it, the spawn succeeds. The suite also covers:orcad-idle-stop-daemon-recovery.test.ts: a late retirement after a timeout, a late refusal, and an unanswered second ask (never replaced).orcad-daemon-retirement.test.ts: the late verdict is reported only after admission has reopened.daemon-init-restart-sequence.test.ts: a targeted restart proceeds over a proven-dead endpoint, still refuses when a probe is inconclusive or a different pid owns the endpoint, and drops a retired legacy generation.daemon-router-retirement.test.ts: a pending router census counts as closed admission.orcad-managed-stop.test.ts: a timed-out outcome still writes a valid stop receipt.Live on omarchy (Linux x64), fully isolated: linux-x64 orcad builds of main and of this branch ran from
/tmp, with their own HOME, XDG dirs andORCA_USER_DATA, a free loopback port,ORCA_ORCAD_MANAGED_ACTIVATION_ROOTset andORCA_E2E_ORCAD_IDLE_TIMEOUT_MS=4000. To open the reconnect window on demand, the script paused the daemon with SIGSTOP during the final idle check, connected a WebSocket client (an E2EE hello, which counts as an open connection), then resumed the daemon. Retirement then committed with the client present. After that, it ranorca terminal createthrough the host's local CLI.terminal daemon on idle stop: retired→ the server stayed up →terminal createfailed with"message": "Terminal daemon is decommissioning".retired→a client returned during idle stop; started a fresh terminal daemon→terminal createreturnedok: true, and reading the terminal showed itsLIVE-OKoutput. Once the terminal was closed and the client had left, the server idled out again normally: the fresh daemonretiredand orcad exited.Not verified live, unit tests only: the lost-reply and late-reply paths (they cannot be timed deterministically on a real host), the legacy-generation drop, and macOS/Windows hosts.