Repository navigation
fix(GAP-03): stop offline reconnect from resurrecting server-closed tabs - #74
svc-orca[bot] wants to merge 23 commits into
Conversation
|
Important Review skippedBot user detected. To trigger a single review, invoke the ⚙️ Run configuration
You can disable this status message by setting the Use the checkbox below for a quick retry:
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. Comment |
ACO structural triage — GAP-03 offline client reconciliationRun: Blast radius (high level)
Findings checklist (requested changes)P0
P1 — still open / reviewer attention
P2
Changes from triageNone (review-only; no remaining clear safe P0 patch). Residual riskNon-AST callers · main export parity · e2e docker not run · rebase churn {
"schema_version": "aco-triage-run/v1",
"run_id": "gap03-tab-reopen-2026-10-14",
"timestamp": "2026-10-14T08:55:21Z",
"prompt_version": "1.2-quick",
"head_sha": "e236c8f2380b58290613195ac55a31c1742f92fa",
"pr": "https://github.com/LesleyMurfin/orca/pull/74",
"graph_mode": "rg-heuristic-non-ast",
"p0_open": 0,
"p1_open": 3,
"verification": "57 unit tests passed",
"changes_made": []
} |
Reproduces the additive-merge bug in adoptStrandedHostPartitionSession (src/shared/workspace-session-stranded-partition-adoption.ts): when a worktree's tabs are all closed server-side while a client is offline, the client's base tabsByWorktree row for that worktree becomes empty, so workspacesTheBaseOwns() no longer protects it and the stale cached ssh:* partition's closed tabs are unconditionally re-adopted into the merged session on reconnect. - RED: closed tabs must not reappear after reconnect (fails today) - GREEN: a genuinely offline-created tab (never synced) must survive reconnect -- the stablyai#12721 non-regression constraint the downstream fix must not break Fixes stablyai#22038. No fix code included; implementation of reconcileLocalTabsWithServerManifest()/hasPendingReconnectionHandshake is a separate downstream task.
ACO follow-up — P1 main-export decline closedHead: What landed
Verification (this workspace)Checklist updateP0
P1
P2
No further code change from this follow-up (parallel commit already matched the intended fix; duplicate local WIP discarded). |
adoptStrandedHostPartitionSession's workspacesTheBaseOwns() only ever protected a worktree from stranded-partition adoption when the base held a non-empty tabsByWorktree row. Once every tab for a worktree closed server-side while a client was offline, the base's row went empty/absent, so the repair no longer protected it and unconditionally folded in whatever tabs were still sitting in the client's stale on-disk ssh:<targetId>/runtime cache from before it disconnected -- resurrecting closed tabs on reconnect and round-tripping them back out via persistWorkspaceSessionByHost to the host partition and every other connected client (stablyai#22038, revive_labs#962). Fix: sshPartitionCatalogAttribution (extracted into a new workspace-session-host-reconciliation.ts module alongside unownedSessionKeys, to keep workspace-session-host-hydration.ts under this repo's 300-line file budget) now also tracks, per SSH host, the worktree ids the repo catalog positively and *currently* confirms belong to exactly that host -- via the row's own executionHostId field, not a bare connectionId fallback. That fallback already treats a legacy catalog row as resolved, which is exactly the pre-stablyai#12723 shape the one-shot gap-fill repair exists for, so it stays on the unconditional path. adoptStrandedHostPartitionSession takes the confirmed set as reconciledWorktreeIds: for one of these ids, an absent tabsByWorktree row on the base is no longer read as an unverifiable stablyai#12721 gap -- it's the server's current truth of zero tabs -- so a host row that still names real (non-empty) tabs is declined rather than adopted. An id the catalog also marks foreign for another host (residue proving a genuine migration) is exempted, so a freshly-moved connection's real tabs still adopt normally. Declining never destroys anything: the id simply drops out of adoptedWorkspaceIds, so partitionRowsTheWriteWontReturn parks the stale row in the write-side shadow the same way a contested id already does. One regression fixture (workspace-session-ssh-partition-ownership.test .ts's "keeps a declined row..." case) gave its sibling worktree a bare `local: session({})` with no tabsByWorktree entry at all for the sibling -- structurally identical to the GAP-03 bug shape itself. Gave it the same known-worktree explicit-empty-row shape already used throughout the round-trip suite (`strandedPartitions()`), which is what that test's own "sibling is adopted and routes back" premise actually depends on. Tests: - workspace-session-host-offline-reconnect.test.ts: RED -> GREEN (closed tab no longer resurrected); stablyai#12721-safety scenario (offline- created, never-synced tab) stays GREEN. - workspace-session-ssh-partition-ownership.test.ts, workspace-session-ssh-partition-round-trip.test.ts, workspace-session-host-split.test.ts, workspace-session-partition-owner.test.ts, ssh-host-partition-session-export.test.ts: all still GREEN, zero regressions. - Also re-ran workspace-session-host-contention.test.ts, workspace-session-host-persistence.test.ts and main/ipc/remote-workspace.test.ts (other direct callers/dependents of the touched functions): all green. - 115/115 across all 9 files above. No PR opened; stops at a locally-ready, committed branch per this slice's constraints.
…rness Adds a two-launch (quit+relaunch) Playwright harness that reproduces the GAP-03 repro exactly: pair a client to a Dockerized SSH host, open N tabs, quit the client, close M tabs' local base row while leaving the SSH host-partition mirror stale (simulating 'closed server-side while offline'), relaunch, and measure resurrection plus the stablyai#12721 non-regression. Modeled on run-ssh-docker-e2e.mjs (wrapper shape), golden-quit-relaunch-session.spec.ts and persisted-session-production-upgrade.spec.ts (two-launch + on-disk fixture injection), and the docker-ssh-relay-* helpers already used by ssh-lost-kill-tab-resurrection.spec.ts. Also hardens docker-ssh-relay-target.ts: some hosts' tmp directories carry a default ACL that overrides ssh-keygen's normal 0600 permissions, which sshd then refuses under StrictModes. One chmodSync call fixes it unconditionally for every SSH-relay Docker e2e spec, not just this one. BEFORE-state (pre-fix, commit 36beb65f6b) RED evidence captured separately; see revive_labs specs/22038-gap-03-docker-cross-site-test-harness.md. Slice A of the GAP-03 before/after test-battery ticket: harness + baseline only. AFTER-state measurement, Calgary-site run, and aggregate report are follow-up slices.
…ises the fix Slice A's harness (commit 8b26787bdc) measured 2/2 tabs resurrected on both pre-fix and (unverified) post-fix code, and offlineDraftSurvived: false even on pre-fix code -- an anomaly flagged for the next agent. Root-caused and fixed three independent harness defects, none in product code: 1. "Closed tabs" fault injection wrote workspaceSession.tabsByWorktree [worktreeId] = [] (a filter of an already-empty array) instead of deleting the key. adoptStrandedHostPartitionSession's GAP-03 decline path keys its verdict on Object.hasOwn(base.tabsByWorktree, workspaceId), and hasOwn is true for a key holding [] exactly as much as a populated array -- silently defeating the fix regardless of which code was under test. Also switched CLOSE_COUNT/TAB_COUNT defaults to close every opened tab (2/2): the fix's own decline logic is all-or-nothing (see its doc comment), so a scenario that leaves any survivor tab in the worktree never reaches the reconciledWorktreeIds path at all, pre- or post-fix, and proves nothing about the regression. 2. The stablyai#12721 offline-draft tab was injected only into legacy tabsByWorktree, never into unifiedTabs/tabGroups/tabGroupLayouts/ activeGroupIdByWorktree/activeTabIdByWorktree. The first launch's own quit-time write already populates unifiedTabs+tabGroups, so buildHydratedTabState takes the unified branch on the second launch and enumerates worktrees from unifiedTabs alone -- a legacy-only row is invisible there, and even where tabsByWorktree is swept for reconciliation, a legacy row with no live PTY and no unified twin is exactly what getOrphanTerminalIds deletes as an orphan on the same boot. This was the flagged anomaly: offlineDraftSurvived: false regardless of which product commit was under test, because the tab never reached a store the modern hydration path reads from. 3. Both SSH targets' activeConnectionIdsAtShutdown entries survived into the fault-injected profile, so the second launch auto- reconnected live SSH and pulled a fresh PTY snapshot straight off the still-alive Docker container (applyDirectSshRemoteWorkspaceSnapshot / mergeDirectSshRemoteWorkspaceSession) -- a code path GAP-03 never touches, since this fixture never actually kills the "closed" PTYs on the container. That live resync silently overwrote whatever the boot-time fetchWorkspaceSessionWithRuntimeHostOwners merge had correctly decided. Clearing the field keeps the second launch on the one path the fix targets: persisted-state hydration at boot, confirmed by temporary instrumentation showing adoptStrandedHostPartitionSession's own "adoptable" set already correctly emptied out before this fix (root-caused, then reverted). Measured clean before/after against the corrected harness by temporarily reverting to 36beb65f6b (fix's own parent) and back: BEFORE (36beb65f6b): resurrectedCount 2/2, offlineDraftSurvived true AFTER (129d11ad4d): resurrectedCount 0/2, offlineDraftSurvived true offlineDraftSurvived is true on BOTH runs, confirming the stablyai#12721 anomaly was a harness defect throughout, not a real regression on either commit. All 64 tests across the fix's own directly-affected unit-test files still pass unchanged. Also updated run-offline-reconnect-tab-resurrection-docker.mjs to stop overriding the spec's own TAB_COUNT/CLOSE_COUNT defaults.
…ing PR stablyai#968 scenarios reconciledWorktreeIdsForHost applied unconditionally to every catalog-confirmed worktree, regardless of whether this client's SSH connection to that host was ever actually interrupted. Since an SSH-hosted worktree's tabsByWorktree row never routes to the 'local' base partition in the first place (buildHostSessionRouting always sends it to ssh:<targetId>), base.tabsByWorktree is absent for EVERY confirmed worktree on EVERY boot -- closed or not. Combined with remote-repo-registration.ts stamping the modern executionHostId field on every SSH repo it registers (not a rare legacy shape), this meant an ordinary quit + relaunch with nothing closed by anyone dropped every still-open tab on every SSH worktree whose repo carries the modern stamp -- the opposite of GAP-03's own goal, and a severe regression the existing 115-test suite, the Docker e2e harness (which always closes every opened tab, CLOSE_COUNT=TAB_COUNT by design) and the 4-client live verification never exercised, because none of them ever tested a worktree with a genuine SURVIVOR tab through the confirmed attribution path. Fix: gate reconciledWorktreeIdsForHost on whether this host's SSH target was actually connected at the client's last shutdown (activeConnectionIdsAtShutdown -- the same field use-app-startup-hydration.ts already reads to decide which targets to auto-reconnect at boot). A target still listed there was never disconnected; its host-partition mirror is this client's own current state written moments before quitting, not a stale pre-offline cache, so an absent base row for it was never evidence of anything and must not be read as "the server's confirmed zero." A target NOT listed there is the actual GAP-03 precondition, and still declines exactly as before. Verification of PR stablyai#968's 4 deferred pr-agent scenarios, RED-then-GREEN against this fix's own parent (36beb65f6b) and current HEAD, per-scenario: - Concurrent Active Edits: the achievable case (host not disconnected -- see the restart regression above) now adopts correctly. The harder case (a live sibling worktree while genuinely offline) is a DOCUMENTED, PROVEN architectural limitation: reconciledWorktreeIdsForHost gates per host, not per worktree, so a worktree the base has never cached is indistinguishable from one whose tabs were genuinely closed while offline. Both are safely parked (never deleted -- attachHostSessionShadow/partitionRowsTheWriteWontReturn), just not adopted this boot. Resolving this needs a live server signal (the upstream draft's own manifestEpoch/handshake proposal); this fix has none. - Split Layout Pruning: proven safe by construction -- GAP-03's decline is per-worktree across every field, not per-tab, so a declined worktree carries no rows in tabsByWorktree, unifiedTabs, tabGroups OR tabGroupLayouts. No orphaned split leaf can survive because nothing about that worktree is adopted at all. - Write Race Protection: CONFIRMED, REPRODUCIBLE, UNFIXED bug. A wake-triggered persistWorkspaceSessionByHost never touches a host partition with nothing else routed to it (no race). The instant anything else legitimately writes to that same host partition in the same call (a sibling worktree gaining a tab), attachHostSessionShadow unconditionally re-attaches EVERY parked row for that host -- including one GAP-03 just correctly declined -- and it round-trips back to the server. A naive "drop instead of park" mitigation was prototyped and reverted: it closes this race for a genuinely-closed worktree but is blind to the Concurrent-Active-Edits false positive, turning a recoverable non-adoption into an unrecoverable delete of another client's live tabs. Answers PR stablyai#968's deferred question directly: hasPendingReconnectionHandshake is NOT unnecessary -- it (or an equivalent live signal) is the only thing that would close this race without risking data loss. - Rapid Sleep/Wake Cycles: verified convergent and non-corrupting across 5 consecutive boot cycles against an unchanged on-disk profile, with no cumulative resurrection and no duplicate adoption. Also adds a real Docker cross-site e2e scenario (ordinary quit + relaunch, nothing closed, connection still active at shutdown) proving the restart regression fix end-to-end, not just at the unit level. New/changed tests: workspace-session-host-offline-reconnect.test.ts (+7), workspace-session-host-write-race.test.ts (new, 2), workspace-session-host- rapid-reconnect-cycles.test.ts (new, 2). All 115 pre-existing GAP-03 tests still pass unchanged; 124 total across the 11 touched files. No PR opened; stays on this pushed branch pending owner sign-off, per this slice's constraints (nothing goes upstream until fully verified).
…e 1) - Add remote-workspace-host-testimony.ts with RemoteWorkspaceTestimonyState and hostHasAnsweredForTarget - Add remote-workspace-host-testimony.test.ts covering unhydrated, conflict, and non-conflict synced states - Update workspace-terminal-host-authority.ts to compose RemoteWorkspaceTestimonyState and delegate to hostHasAnsweredForTarget (behavior-neutral)
- Add workspace-session-host-shadow-testimony.ts implementing shadowRowsTheHostHasNotAnswered - Export WORKTREE_KEYED_FIELDS from workspace-session-host-contention.ts - Extend HostPersistenceState with RemoteWorkspaceTestimonyState and wire shadow filtering into splitWorkspaceSessionForWrite in workspace-session-host-persistence.ts - Add write-race protection test, retitle existing test, and update docstring verdict in workspace-session-host-write-race.test.ts
… decline loop
CodeRabbit finding: the reconciled-decline loop in adoptStrandedHostPartitionSession
read host.tabsByWorktree[workspaceId] with the bare normalized workspaceId, but
tabsByWorktree keys may be stored in canonical worktree:${workspaceId} form (the
type's own doc comment says so). The adoption loop a few lines down already
normalizes every key before comparing; the decline loop didn't, so a canonical-keyed
host row silently defeated GAP-03's whole decline and adopted stale tabs it should
have declined.
Red test added first (workspace-session-host-offline-reconnect.test.ts): a host
partition with tabsByWorktree keyed as `worktree:${WORKTREE_ID}` under the
confirmed-disconnected-at-shutdown GAP-03 precondition. Failed before the fix
(both tabs survived, just under the canonical key instead of the bare one, so the
naive same-key assertion would have missed it -- the test walks every surviving
tab across every key). Fix: walk host.tabsByWorktree entries and normalize each
key via normalizeWorkspaceSessionKeyToWorkspaceId before matching against
workspaceId, matching the adoption loop's own pattern.
8/8 in the touched suite green; 75/75 across the 7 directly-dependent partition/
reconciliation suites green.
… worktree rows CodeRabbit finding: partitionRowsTheWriteWontReturn only walked WORKTREE_KEYED_FIELDS for a declined worktree, leaving tab-keyed (terminalLayoutsByTabId, remoteSessionIdsByTabId, localOnlyScrollbackByTabId) and pane-keyed (terminalPtyIncarnationsByPaneKey) rows unparked. If another workspace caused a write to the same SSH partition before host testimony arrived, the write retained the declined tabs but dropped their layout/session rows, leaving inconsistent state. Fix: 1. partitionRowsTheWriteWontReturn now maps each tab- and pane-keyed entry to its worktree via buildWorktreeIdByTabId / worktreeIdForPaneKey and parks rows whose owning worktree was declined. 2. attachHostSessionShadow and shadowRowsTheHostHasNotAnswered now iterate PARKABLE_HOST_SESSION_FIELDS (worktreeKeyed, tabKeyed, paneKeyed) so dependent rows are restored alongside tabsByWorktree before testimony and withheld after. 3. Added red-first regression test in workspace-session-host-write-race.test.ts proving terminalLayoutsByTabId and remoteSessionIdsByTabId survive the write.
…n activeConnectionIdsAtShutdown is undefined
CodeRabbit finding: when session.activeConnectionIdsAtShutdown is undefined
(older persisted sessions, interrupted shutdowns), hydration defaulted to an
empty set and passed false ("was disconnected") into reconciledWorktreeIdsForHost.
This caused every confirmed worktree on that host to be reconciled and declined,
dropping still-open tabs for sessions where shutdown status was merely unknown
rather than explicitly disconnected.
Fix:
1. wasTargetConnectedAtLastShutdown helper in workspace-session-host-hydration.ts:
when activeConnectionIdsAtShutdown is defined, check membership as before.
When undefined, check session.remoteSessionIdsByTabId for any surviving remote
PTY session belonging to targetId (matching the established pattern in
workspace-session-reconnect-targets.ts and ssh-pane-connect-gate.ts).
2. Added red-first regression test in workspace-session-host-offline-reconnect.test.ts
proving tabs survive when activeConnectionIdsAtShutdown is undefined but
remoteSessionIdsByTabId contains a session for the target.
9/9 in touched suite green, 55/55 across 5 related hydration/reconciliation suites green.
… live snapshot arrival CodeRabbit finding 3: an api.set/patch call generated while hostHasAnsweredForTarget was false (so declined tabs were attached by the shadow) could be delivered AFTER the live snapshot landed and testimony flipped true. Nothing ordered in-flight writes against the live snapshot's arrival, so a delayed pre-testimony write could overwrite the authoritative state and resurrect closed tabs. Fix: 1. enqueueHostPartitionWrite in workspace-session-host-persistence.ts serializes writes per host partition and tracks monotonic write generations. 2. When a write was prepared without host testimony, it checks right before dispatch whether testimony has since arrived or a newer write was dispatched for that host; if so, the stale pre-testimony write is discarded instead of overwriting the authoritative snapshot. 3. Added red-first regression test in workspace-session-host-write-race.test.ts proving a deferred pre-testimony write queued behind an in-flight write does not overwrite the live snapshot when testimony arrives before delivery. 55/55 tests across 5 related persistence/contention/write-race suites green.
…imony arrives Pullfrog finding 6: shadowRowsTheHostHasNotAnswered dropped ALL non-folder parked rows once hostHasAnsweredForTarget became true. Because direct-SSH snapshots only project terminal state (remote-workspace-snapshot-apply.ts:201), non-terminal state such as openFilesByWorktree editor drafts is never reported by or superseded by the SSH host snapshot. Withholding those non-terminal rows caused subsequent partition writes to erase offline-created editor drafts from disk. Fix: 1. Define SSH_TERMINAL_TESTIMONY_FIELDS in workspace-session-host-shadow-testimony.ts: only terminal-scoped fields that SSH snapshots actually manage are withheld by host testimony. Non-terminal fields like openFilesByWorktree survive testimony arrival. 2. Added unit test in workspace-session-host-shadow-testimony.test.ts verifying openFilesByWorktree survives even after testimony lands for that target. 3. Updated test comment in workspace-session-host-offline-reconnect.test.ts to accurately state the testimony boundary.
- Extract `enqueueHostPartitionWrite` and write chain state to `workspace-session-host-write-queue.ts` to bring `workspace-session-host-persistence.ts` within the 300-line budget (252 lines). - Deduplicate imports and add `SAFETY:` comments to all type assertions across touched files to satisfy `check-changed-code-quality` rules. - Add RED-first unit tests for `workspace-session-host-write-queue` verifying FIFO serialization, independent host concurrency, generation supersession, and test state reset. - Verified: 138/138 tests passing across all 13 session suites, typecheck clean, and 0 findings in `check-changed-code-quality`.
…hydration, and adoption - Guard host write generation advancement by replaceable/explicit-generation flag, preventing non-replaceable partial patches from superseding durable persists. - Support live testimony state resolution via getLiveState thunk and global provider in enqueueHostPartitionWrite to discard stale pre-testimony writes upon testimony arrival. - Normalize tab- and pane-keyed worktree ids in partitionRowsTheWriteWontReturn and resolve worktree claims in attachHostSessionShadow. - Preserve stablyai#12723 empty placeholder rows in base tabsByWorktree during stranded adoption. - Extract partitionRowsTheWriteWontReturn to workspace-session-stranded-partition-parking.ts to honor max-lines budget without disabling rules. - Guarantee Docker cleanup in e2e spec via nested finally blocks. - Approved by riley-pr-agent, riley-pr-check, and riley-the-maintainer.
…adow rows and clamp CLOSE_COUNT - Track whether host partition slices actually receive parked shadow rows in attachHostSessionShadow. - Propagate carriesParkedShadowRows through splitWorkspaceSessionForWrite to enqueueHostPartitionWrite. - Gate pre-testimony write discarding on carriesParkedShadowRows, preventing data loss for live local edits and delta patches. - Add regression test ensuring queued pre-testimony writes without parked shadow rows run when live testimony arrives. - Safely parse TAB_COUNT and clamp CLOSE_COUNT to TAB_COUNT in Docker e2e harness.
…ale docker runner env - Narrow SSH_TERMINAL_TESTIMONY_FIELDS in workspace-session-host-shadow-testimony.ts to terminal fields managed by direct-SSH snapshots, preserving client-only state such as localOnlyScrollbackByTabId. - Export isShadowRowSubjectToHostTestimony as a single shared authority for testimony invalidation. - Track hostsWithTestimonyInvalidatableShadow in attachHostSessionShadow, ensuring writes carrying only preserved shadow entries (folders, editor drafts) are not discarded upon testimony arrival. - Add required typescript(consistent-type-assertions) safety comment on splitWorkspaceSessionForWrite call in patchWorkspaceSessionByHost. - Remove dead ORCA_GAP03_CLOSE_COUNT forwarding in config/scripts/run-offline-reconnect-tab-resurrection-docker.mjs.
…imony modules - Move PARKABLE_HOST_SESSION_FIELDS to src/shared/workspace-session-host-field-ownership.ts alongside other ownership field sets. - Re-export PARKABLE_HOST_SESSION_FIELDS from workspace-session-host-contention.ts for backwards compatibility. - Import PARKABLE_HOST_SESSION_FIELDS directly from shared in workspace-session-host-shadow-testimony.ts and merge duplicate imports. - Satisfies oxlint audit:code-quality:native with deny-warnings.
… tabs Primary setForConnectedTargets omitted session, so main persistedSessionForTarget re-adopted stale ssh-partition tabs without reconciledWorktreeIds. Pass the renderer session on upload and apply the same confirmed+shutdown decline on the main fallback; regression covers omit-session offline vs connected restart.
Maintainer follow-up — main merge + clean workspaceHead: Merge resolution (GAP-03 × main)
VerificationChecklist
Merge remains Lesley-only. |
Pre-testimony write discard is opt-in: only callers that know the write carries parked shadow rows pass true (persistence uses hostsWithTestimonyInvalidatableShadow). Default false keeps ordinary patches from being dropped when live testimony lands mid-flight. Also: GAP-03 decline reads originalBase for hasBaseKey so main's ownedSessionKeys superseding of empty stablyai#12723 placeholders cannot false-trigger offline decline; export GAP-03 tests use createSqliteTestStore.
3f96782 to
5a89d75
Compare
Parked unifiedTabs/tabGroups/tabGroupLayouts/activeGroupIdByWorktree must drop from the write shadow once the host has answered, same as declined tabs — else post-testimony persist restores ghost split chrome with no tabs.
Maintainer pass (post-rebase)State: rebased onto ACO P1 checklist
Verification75 targeted unit tests passed across write-queue, shadow-testimony, offline-reconnect, write-race, rapid-reconnect, testimony, contention, ssh export, partition ownership. No open review threads. Ready for Lesley merge review (agents do not merge). |
Correctness follow-up (reviewer P1s)Head: Addressed
Verification
Still residual (P2, non-blocking)
Workspace clean. Merge Lesley-only when GitHub mergeable settles. |
Precompute base tabsByWorktree workspace ids and normalize reconciled ids before the offline decline loop. Drops the worktree: dual-key scan and the unused hydration re-export of wasTargetConnectedAtLastShutdown.
Follow-up elegance pass
|
ELI5
If you close a tab on the server while another Orca window is offline, that offline window used to bring the closed tab back when it reconnected. This change teaches reconnect to trust the server’s current tab list after a real offline gap, without treating a normal quit-and-relaunch as “offline” and wiping still-open tabs.
What Changed
Before
After
reconciledWorktreeIdsForHost+wasTargetConnectedAtLastShutdown).remoteSessionIdsByTabId.Mechanism
Why
GAP-03 / stablyai#22038 (and related stablyai#21341): stale offline client cache must not override server closes on reconnect.
Alternatives considered:
Linked Issue
Fixes stablyai#22038
Related: stablyai#21341, stablyai#22333 (documented concurrent-edit park boundary)
Visual Proof
N/A — session hydration/persistence behavior; no UI chrome change. Covered by unit tests + docker offline-reconnect harness (not run in this triage pass).
Testing
Commands run in triage:
Platforms exercised by tests: unit (Linux agent host). Docker e2e harness present (
tests/e2e/gap-03-offline-reconnect-tab-resurrection.spec.ts) not executed in triage.AI Disclosure
ACO structural triage + PR authoring via agent (xai grok) on worktree
orca-gap03-tab-reopen.Review
ACO triage (2026-10-14) summary
persistedSessionForTargetstill adopts withoutreconciledWorktreeIds(export ≠ boot decline)carriesParkedShadowRows=trueis a future-caller contract hazard (production sets explicit flag)origin/mainat PR open — expect rebase/CI noiseAgent skill upstream boundary
docs/reference/agent-skill-sharing-upstream-boundary.mdand copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.Notes
SSH/remote session partition ownership is the core surface. Cross-platform: persistence paths are shared renderer/shared; Windows/macOS not separately re-run here.
Checklist
N/Awith reasonpnpm lint,pnpm typecheck,pnpm test, andpnpm buildpass (or CI will cover; local preferred) — triage ran targeted unit tests only