[Bug]: Deleting a workspace on a remote runtime host leaves orphaned session keys that re-render as permanent "Unknown" ghost cards
Operating system
macOS
Orca version
1.4.215
Details
Short summary:
Deleting a workspace that lives on a remote runtime host removes the repo/worktree records but leaves two orphaned maps behind in that host's session document. The sidebar re-renders those orphans as permanent ghost cards under an "Unknown" heading. They survive workspace deletion, app restart, and even the orca-data.json → profile-state.db migration. Neither the UI nor the CLI can remove them.
Both leaked maps declare onRepoRemoval: 'prunedByOwnerKey' in Orca's own policy table, so this is a violation of the declared cleanup contract rather than intended behaviour.
What happened?
Two workspaces on a remote runtime host were deleted. Afterwards the sidebar still shows two cards, each under its own Unknown group header, each badged with the remote host name and shown in the sleeping (moon) state. Deleting them again from the sidebar has no lasting effect — they come back.
Orca's authoritative data confirms the delete did succeed:
orca repo list --environment <host> # 5 repos, neither ghost present
orca worktree ps --environment <host> # 12 worktrees, neither ghost present
orca terminal list --environment <host> # 10 terminals, neither ghost present
orca worktree show --worktree id:<repoA>::<pathA> -> selector_not_foundworktreeMeta, projects and repos in the profile state store contain no trace of them either. The cards are rendered purely from leftover session keys.
Root cause
Local-host session state and remote-host session state live in two different documents:
local host -> workspaceSession
remote host -> workspaceSessionsByHostId[<hostId>]In my profile the workspaceSession document is 100% clean — every per-worktree map contains exactly the live local worktrees plus the pseudo-terminal ids. Every leaked key is in workspaceSessionsByHostId.
Within the remote host's document, the delete cleanup did run and correctly pruned tabsByWorktree, unifiedTabs, tabGroups, tabGroupLayouts, activeTabIdByWorktree, activeGroupIdByWorktree, lastVisitedAtByWorktreeId, terminalLayoutsByTabId, activeBrowserTabIdByWorktree — but missed exactly two:
| map | state after deleting both workspaces |
|---|---|
activeTabTypeByWorktree |
10 keys = 8 live + 2 orphans |
terminalTopologyRevisionByRepoId |
2 keys = 2 orphans, 0 live |
The second row is the clearest signal. Compare the same map in the local document, where it is maintained correctly and contains both live local repo ids:
local terminalTopologyRevisionByRepoId: { <liveRepo1>: 1, <liveRepo2>: 15, "global-floating-terminal": 45, "ephemeral-setup-terminal:...": ... }
remote terminalTopologyRevisionByRepoId: { <deletedRepoA>: 4, <deletedRepoB>: 1 } # none of the 5 live remote reposA normally-maintained map cannot end up containing only deleted repos and none of the live ones.
Code references (app.asar, 1.4.215)
Both leaked maps declare that they must be pruned on repo removal:
activeTabTypeByWorktree: {onRepoRemoval:`prunedByOwnerKey`, onTransfer:`copiedByOwnerKey`}
terminalTopologyRevisionByRepoId: {onRepoRemoval:`prunedByOwnerKey`, onTransfer:`copiedByOwnerKey`}terminalTopologyRevisionByRepoId is additionally classified as not participating in cross-host sync, in two separate tables:
terminalTopologyRevisionByRepoId: `hostPrivate` // scope table
terminalTopologyRevisionByRepoId: `none` // sync-projection tableand there is a hardcoded exclusion:
const cPe = ZNe.filter(e => e !== `terminalTopologyRevisionByRepoId`
&& e !== `lastVisitedAtByWorktreeId`);I could not trace the exact call path that skips activeTabTypeByWorktree on the remote document from the minified bundle, so treat that specific mechanism as inference. The declaration-vs-observed-state mismatch above is directly verifiable.
Additional observation
The purely local delete path leaks too, but harmlessly: ui.manualRepoOrder still holds an entry for a deleted local repo id. Because ui is a global document and manualRepoOrder only affects ordering, no card is rendered. Only leaks into a host-scoped session map produce a visible ghost card — which is why this reproduces on remote runtime hosts and not locally.
How can we reproduce it?
- Connect a remote runtime host.
- Create a workspace on that host, open a terminal in it (so tab/topology state is written).
- Delete the workspace.
- Restart Orca.
- The workspace reappears in the sidebar under an
Unknowngroup header, badged with the remote host, in the sleeping state. Deleting it again does not persist.
Workaround
Quit Orca, then prune the orphan keys from
~/Library/Application Support/orca/profiles/<profile>/profile-state.db
(domains ui, workspaceSession, workspaceSessionsByHostId), recomputing each row's content_hash as sha256(payload) and bumping revision. Restart.
Note that the workaround given in #20381 — hand-editing orca-data.json — no longer works on 1.4.215, because the profile state has migrated to SQLite (profile-state.db). The stale orca-data.json is left on disk and edits to it are ignored.
Suggested fix
Apply the declared onRepoRemoval: 'prunedByOwnerKey' policy uniformly to every entry in workspaceSessionsByHostId, not just to the local workspaceSession document. A reconcile pass that iterates over currently known repo ids will never reach an orphan, so pruning has to be driven from the removal event itself.
Related issues
Same "Unknown" symptom, different causes — none of them covers the remote-runtime delete path or the SQLite store:
- #20381 — names the exact same two maps, but the cause is an interrupted add project flow, and it targets
orca-data.json - #20102 —
project setup-deleteleaves an "Unknown" heading, CLI reportsselector_not_found - #22712 — deleting while a terminal is attached leaves a blank card
- #23224 —
deleteSetupnot callingnotifyReposChanged(); that variant is cleared by Force Reload, this one is not - #23653 — undescribed report of the same symptom
Anything else that might help
- Orca 1.4.215, macOS 27.0.0 (arm64)
- Remote host is a Linux runtime host (
terminalPlatform: linux) - Identifiers above are redacted placeholders; the underlying values are UUIDs and absolute paths
Source: stablyai/orca