Repository navigation
Description
Where did this happen?
Desktop app (macOS)
Version
1.17.0
What happened?
Hovering a workspace row in the sidebar reveals a small − button (tooltip: "Remove from sidebar"). One click on it, with no confirmation and no undo:
- Replaces the workspace's entire
paneLayoutwith an empty one. Every tab and split in that workspace is discarded from local state. - Tears down the pane runtimes — releases every terminal runtime and
destroy()s every browser pane. - Is not recoverable. Re-adding the workspace to the sidebar only flips
isHiddenback tofalse;paneLayoutis never restored. You get the workspace back, empty.
Meanwhile the PTYs behind those terminals are not killed — terminalRuntimeRegistry.release() explicitly does not tell host-service to kill the PTY. So any Claude Code / Codex sessions that were running keep running, headless, with no pane referencing them. (Corrected — see comment below: they are recoverable via the "N background terminal sessions" button, which I missed when filing. The layout loss below stands; the session loss does not.)
The comment that justifies skipping a confirmation dialog is this one, in RemoveFromSidebarMount.tsx:
Unpinning is reversible — the workspace itself is not deleted and can be re-pinned from the Workspaces page or sidebar — so it runs immediately with no confirmation dialog.
That premise is true of the workspace record and false of everything in it. The same flow's tombstone path wipes paneLayout and disposes the runtimes. The action is documented as reversible and implemented as destructive.
Code path (verified at 97e0446, and byte-identical in the shipped 1.17.0 app.asar)
DashboardSidebarExpandedWorkspaceRow.tsx:296-315 — the − button, onClick → onRemoveFromSidebarClick(), no guard.
RemoveFromSidebarMount.tsx:20-29 — fires immediately on intent:
navigateAwayFromWorkspace(target.workspaceId);
hideWorkspaceInSidebar(target.workspaceId, target.projectId);
clear();sidebarMutations.ts:56-65 — tombstoneSidebarWorkspaceRecord:
cleanupPaneRuntimes([existing]);
collections.v2WorkspaceLocalState.update(workspaceId, (draft) => {
draft.sidebarState.isHidden = true;
draft.paneLayout = createEmptyPaneLayout(); // <-- every tab and split, gone
});useDashboardSidebarState.ts:180-188 — cleanupWorkspacePaneRuntimes:
for (const terminalId of extractPaneIds(rows, getTerminalRuntimeId))
terminalRuntimeRegistry.release(terminalId);
for (const browserId of extractPaneIds(rows, getBrowserRuntimeId))
browserRuntimeRegistry.destroy(browserId);terminal-runtime-registry.ts:400-404 — why the agents survive but become unreachable:
Release the renderer-side terminal runtime only. This detaches the xterm
view and closes the WebSocket, but it does not tell host-service to kill
the underlying PTY.
useDashboardSidebarState.ts:131-167 — ensureSidebarWorkspaceRecord, the re-add path. It sets isHidden = false and nothing else. There is no branch that rebuilds paneLayout, so the layout is unrecoverable once tombstoned.
The target itself
- The
−issize-3.5(14px), rendered insideinvisible … group-hover:visible, so it only exists while the pointer is over the row. - On a main workspace row it is the only hover action, and it sits immediately right of the
⌘Nshortcut hint (see screenshot). - It carries the same visual weight as the non-destructive row affordances next to it — nothing marks it as the one that discards state.
Why I'm writing this up in detail
I lost a full working set this way in one mis-click: multiple tabs and splits with long-running agents in them, no dialog, no undo, no restore path. It cost me enough trust in the app that I've moved my daily terminal elsewhere for now. I'd rather see this fixed than leave, hence the writeup rather than a one-liner.
Steps to reproduce
- Open a workspace and build up real state in it: several tabs, a couple of splits, a few agents running.
- Move the pointer over that workspace's row in the sidebar.
- Click the
−that appears on the right of the row. - The workspace disappears from the sidebar. No dialog, no toast, no undo.
- Re-add it from the Workspaces page. It comes back with an empty layout — every tab and split is gone.
pson the host: the agent processes from step 1 are still running, with no pane in the app bound to them.
Logs, screenshots, or recordings
The − next to ⌘1 is the button. Repo and workspace names are redacted.
Suggested fixes
Roughly in order of value:
- Don't destroy contents on hide. Keep
paneLayouton the tombstone row and restore it inensureSidebarWorkspaceRecord. Hiding then means what the comment already claims it means: a visibility flag. This alone removes the data loss. - If the layout genuinely has to be dropped, gate it. Either an
AlertDialog(already the pattern inDeleteWorkspaceDialog) or an undo toast —sonner'saction: { label, onClick }is already used in ~9 places in this codebase. - Say what is at stake in that confirm, e.g. "3 agents are still running in this workspace" / "12 tabs will be closed". The counts are already available on the row.
- Reconsider the affordance. A bare hover-revealed 14px icon button, sitting next to a keyboard-shortcut hint, is a low bar for an irreversible action. The context menu would be a safer home for it.
Related: the running-agents chip on the same row has a similar problem — filed separately as #5990.
Happy to send a PR for (1) + (2) if that would help.

Activity
fix(desktop): keep the pane layout when hiding a workspace from the s…
2solarmax commentedon Jul 27, 2026
Correction from me (the reporter): one claim in the original report was wrong, and it lowers the severity.
I wrote that the orphaned sessions have "no UI anywhere in the app to reattach to them." That is false.
BackgroundTerminalsButtondoes exactly that:terminal.countBackgroundSessions/terminal.listSessionsand computesgetBackgroundTerminalSessions(sessions, attachedTerminalIds)— every live session in the workspace not currently bound to a pane;N background terminal session(s)whenever that count is non-zero;handleAdopt(terminalId)→focusOrAddTerminalPane(store, terminalId)puts one back into a pane.So after a "Remove from sidebar", re-adding the workspace shows that button with every still-running session behind it, and they can be adopted back one at a time. I grepped for
orphan/reattach/recover sessionand concluded the feature was absent; it is called "background sessions" and "adopt". That is on me, and I'd rather flag it than let the report stand on a wrong premise.What this changes: the running agents are recoverable. This is not the total loss I described.
What still stands, unchanged:
draft.paneLayout = createEmptyPaneLayout()still discards the workspace's tab and split structure irreversibly. Adoption rebuilds sessions one at a time into a fresh layout; it does not restore which pane was where, or the splits. On a workspace with a dozen panes that is still real, unrecoverable work.destroy()ed with no equivalent of adoption.Adjusted ask: fix (1) in the original list — keep
paneLayouton the tombstone row and restore it inensureSidebarWorkspaceRecord— is still the right fix and now the only one strictly needed. Failing that, an undo toast that says what happened would close most of the gap, since the recovery mechanism already exists and just isn't surfaced at the moment it becomes relevant.Verified against the shipped 1.17.0
app.asar, same as the original report.