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
[bug] Deleting a workspace leaves its git branch behind, so reusing the workspace name creates a mismatched branch #6380
superset ws delete removes the workspace record and the worktree directory, but it leaves the local git branch behind in the parent repository.
Because the branch still exists, creating a new workspace with the same name does not reuse the name. Superset silently picks a suffixed branch instead: <name>-2, then <name>-3, and so on. The workspace name stays the same, so the workspace name and its branch no longer agree.
In the desktop app the same flow is worse: after I delete a workspace and create a new one with the same name, the app lands on a "Workspace not found" screen showing the old workspace ID (aa511abc-07ec-41e6-9976-45e603fb170b), and I cannot get into the new workspace.
I expect a deleted workspace to release its branch, so the same name is genuinely free to use again.
The worktree directories under ~/.superset/worktrees/<project>/ are removed correctly, and the project teardown script runs correctly. Only the branch survives.
Expected
One of:
Delete the branch with the workspace when the branch holds no unmerged work, or
Ask the user whether to keep or delete the branch, or
At minimum, report a warning that names the leftover branch, instead of silently renaming the new workspace's branch.
The report is consistent with the code. Local branch deletion on workspace destroy is strictly opt-in (deleteBranch), and the non-interactive surface used by superset ws delete hardcodes it to false with no CLI flag and no way to observe the leftover ref — so the branch always survives, and the next create with the same name collides with it and gets deduplicated to -2/-3. The desktop "Workspace not found showing the old workspace ID" looks like a second, separate bug rather than the same symptom: deleted workspaces are tombstoned (not deleted) rows, and the create-time branch idempotency lookup does not exclude tombstones, so a create that resolves to a tombstoned row's branch returns the old, archived workspace id, which the renderer then navigates to and the workspace list can't resolve.
Affected code
Branch survives the delete
packages/cli/src/commands/workspaces/delete/command.ts:9-12 — options are only --host / --local; there is no branch flag, and command.ts:35 calls workspace.delete.mutate({ id }).
packages/host-service/src/trpc/router/workspace-cleanup/workspace-cleanup.ts:514-532 — step 5 runs only when input.deleteBranch is set, and pushes a warning only when the delete throws. With deleteBranch: false there is no code path that can produce a warning about the surviving ref, which matches the observed "warnings": [].
packages/host-service/src/runtime/archived-workspace-reconcile.ts:52-57 — the crash reconciler also passes deleteBranch: false, so nothing else reclaims it later.
Desktop's checkbox is backed by a preference that defaults to false (apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts:360), so the same leftover happens in the app unless the user opts in.
The -2 suffix
packages/host-service/src/trpc/router/workspaces/workspaces.ts:840-874 — for a typed --branch dupe-repro, planBranchSource looks for a ref literally named dupe-repro. The leftover ref is the prefixedcry0genic/dupe-repro, so no existing branch is found → usedExistingBranch: false → the project prefix is applied → deduplicateBranchName("cry0genic/dupe-repro", existing).
packages/host-service/src/trpc/router/workspace-creation/utils/sanitize-branch.ts:35-38 — appends -2, -3, … against the live branch list. input.name is never deduplicated, which is exactly the reported name/branch divergence (and the worktree dir becomes .../cry0genic/dupe-repro-2, since the path is derived from the resolved branch at workspaces.ts:943-947).
Desktop "Workspace not found" with the old ID
packages/host-service/src/trpc/router/workspaces/workspaces.ts:156-170 — findExistingWorkspaceByBranch matches on (projectId, branch) with noisNull(workspaces.archivedAt) filter, so it can return a tombstoned row and workspaces.ts:907-914 sets alreadyExists = true on it.
Compare packages/host-service/src/trpc/router/workspace-creation/shared/adopt-existing-worktree.ts:123, :142, :223 — the adopt path does exclude tombstones ("Tombstones aren't phantoms — same-branch history must survive re-creating a workspace on that branch"). The two lookups disagree.
packages/host-service/src/workspaces/local-workspace-store.ts:227-254 — delete sets archivedAt; the row is never purged.
apps/desktop/.../useSubmitWorkspace/useSubmitWorkspace.ts:166-183 — "The server can resolve the optimistic workspace to a different canonical id; follow it only if we're still on the optimistic route" → the renderer navigates to whatever id create returned, including a tombstone's.
packages/host-service/src/trpc/router/workspace/workspace.ts:43-52 — list filters archivedAt IS NULL by default, so the layout at apps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/layout.tsx:83-96 finds nothing and renders WorkspaceNotFoundState with that stale id.
This second path triggers whenever the create resolves to the exact branch string of a tombstoned row — e.g. typing or picking the full prefixed cry0genic/dupe-repro (which the branch picker still offers, precisely because step 5 left the ref behind). If in your desktop repro you left the branch auto-derived from the name, dedupe would have produced -2 and a fresh row instead, and the not-found screen would need a different explanation — the create response (workspace.id, alreadyExists) and the branch shown in the modal at submit time would settle it.
Ruled out
Worktree removal and teardown: step 3b removes the worktree and verifies deregistration (workspace-cleanup.ts:465-493), consistent with "worktree directories are removed correctly".
gitDeleteBranchTask itself (packages/host-service/src/workers/tasks/git.ts:158-174) is fine — it probes branch --list and then branch -D. It simply is never invoked on this path.
Reproduction
CLI/host level, in the style of packages/host-service/test/integration/workspace-create-delete.integration.test.ts:
// 1. leftover branchconstfirst=awaitscenario.host.trpc.workspaces.create.mutate({projectId: scenario.projectId,name: "dupe-repro",branch: "dupe-repro",});awaitscenario.host.trpc.workspace.delete.mutate({id: first.workspace.id});expect((awaitscenario.repo.git.branchLocal()).all).not.toContain(first.workspace.branch);// currently FAILS// 2. suffix driftconstsecond=awaitscenario.host.trpc.workspaces.create.mutate({projectId: scenario.projectId,name: "dupe-repro",branch: "dupe-repro",});expect(second.workspace.branch).toBe(first.workspace.branch);// currently -2// 3. tombstone idempotency (independent of 1 & 2)constthird=awaitscenario.host.trpc.workspaces.create.mutate({projectId: scenario.projectId,name: "dupe-repro",branch: first.workspace.branch,// exact archived branch});expect(third.workspace.id).not.toBe(first.workspace.id);// currently FAILS
Test 3 is the one that reproduces the desktop symptom at the host level, and it fails independently of whether the branch is cleaned up.
Suggested fix direction
Never return a tombstone from create — add isNull(workspaces.archivedAt) to findExistingWorkspaceByBranch (workspaces.ts:161-168) so it matches adoptExistingWorktree's existing convention. This is the smallest, highest-value change: it stops the app from routing users to a deleted workspace id.
Always report the surviving branch. In workspace-cleanup.ts:514-532, when deleteBranch is false (or the delete is skipped), probe the ref and push a warning naming it — the branch --list check inside gitDeleteBranchTask is already the right primitive to factor out. That alone satisfies the "at minimum" ask and makes the CLI's empty warnings array honest.
Give the CLI the opt-in. Add --delete-branch to packages/cli/src/commands/workspaces/delete/command.ts and thread it through workspace.delete's input (defaulting to false to preserve the current contract) into destroyWorkspace.
Optional, for the "delete when no unmerged work" variant: workspaceCleanup.inspect already computes hasUnpushedCommits (workspace-cleanup.ts:123-136), so an auto-delete could be gated on it — though note step 5 deliberately uses -D today because the checkbox is the consent, so any auto-delete should be a distinct policy rather than a change to that flag's semantics.
Activity
github-actions commentedon Aug 12, 2026
Summary
The report is consistent with the code. Local branch deletion on workspace destroy is strictly opt-in (
deleteBranch), and the non-interactive surface used bysuperset ws deletehardcodes it tofalsewith no CLI flag and no way to observe the leftover ref — so the branch always survives, and the next create with the same name collides with it and gets deduplicated to-2/-3. The desktop "Workspace not found showing the old workspace ID" looks like a second, separate bug rather than the same symptom: deleted workspaces are tombstoned (not deleted) rows, and the create-time branch idempotency lookup does not exclude tombstones, so a create that resolves to a tombstoned row's branch returns the old, archived workspace id, which the renderer then navigates to and the workspace list can't resolve.Affected code
Branch survives the delete
packages/cli/src/commands/workspaces/delete/command.ts:9-12— options are only--host/--local; there is no branch flag, andcommand.ts:35callsworkspace.delete.mutate({ id }).packages/host-service/src/trpc/router/workspace/workspace.ts:178-192— the legacy external surface (CLI/SDK/MCP) hardcodesdeleteBranch: false,force: true,teardownMode: "best-effort".packages/host-service/src/trpc/router/workspace-cleanup/workspace-cleanup.ts:514-532— step 5 runs only wheninput.deleteBranchis set, and pushes a warning only when the delete throws. WithdeleteBranch: falsethere is no code path that can produce a warning about the surviving ref, which matches the observed"warnings": [].packages/host-service/src/runtime/archived-workspace-reconcile.ts:52-57— the crash reconciler also passesdeleteBranch: false, so nothing else reclaims it later.false(apps/desktop/src/renderer/routes/_authenticated/providers/CollectionsProvider/dashboardSidebarLocal/schema.ts:360), so the same leftover happens in the app unless the user opts in.The
-2suffixpackages/host-service/src/trpc/router/workspaces/workspaces.ts:840-874— for a typed--branch dupe-repro,planBranchSourcelooks for a ref literally nameddupe-repro. The leftover ref is the prefixedcry0genic/dupe-repro, so no existing branch is found →usedExistingBranch: false→ the project prefix is applied →deduplicateBranchName("cry0genic/dupe-repro", existing).packages/host-service/src/trpc/router/workspace-creation/utils/sanitize-branch.ts:35-38— appends-2,-3, … against the live branch list.input.nameis never deduplicated, which is exactly the reported name/branch divergence (and the worktree dir becomes.../cry0genic/dupe-repro-2, since the path is derived from the resolved branch atworkspaces.ts:943-947).Desktop "Workspace not found" with the old ID
packages/host-service/src/trpc/router/workspaces/workspaces.ts:156-170—findExistingWorkspaceByBranchmatches on(projectId, branch)with noisNull(workspaces.archivedAt)filter, so it can return a tombstoned row andworkspaces.ts:907-914setsalreadyExists = trueon it.packages/host-service/src/trpc/router/workspace-creation/shared/adopt-existing-worktree.ts:123,:142,:223— the adopt path does exclude tombstones ("Tombstones aren't phantoms — same-branch history must survive re-creating a workspace on that branch"). The two lookups disagree.packages/host-service/src/workspaces/local-workspace-store.ts:227-254— delete setsarchivedAt; the row is never purged.apps/desktop/.../useSubmitWorkspace/useSubmitWorkspace.ts:166-183— "The server can resolve the optimistic workspace to a different canonical id; follow it only if we're still on the optimistic route" → the renderer navigates to whatever id create returned, including a tombstone's.packages/host-service/src/trpc/router/workspace/workspace.ts:43-52—listfiltersarchivedAt IS NULLby default, so the layout atapps/desktop/src/renderer/routes/_authenticated/_dashboard/v2-workspace/layout.tsx:83-96finds nothing and rendersWorkspaceNotFoundStatewith that stale id.This second path triggers whenever the create resolves to the exact branch string of a tombstoned row — e.g. typing or picking the full prefixed
cry0genic/dupe-repro(which the branch picker still offers, precisely because step 5 left the ref behind). If in your desktop repro you left the branch auto-derived from the name, dedupe would have produced-2and a fresh row instead, and the not-found screen would need a different explanation — the create response (workspace.id,alreadyExists) and the branch shown in the modal at submit time would settle it.Ruled out
workspace-cleanup.ts:465-493), consistent with "worktree directories are removed correctly".gitDeleteBranchTaskitself (packages/host-service/src/workers/tasks/git.ts:158-174) is fine — it probesbranch --listand thenbranch -D. It simply is never invoked on this path.Reproduction
CLI/host level, in the style of
packages/host-service/test/integration/workspace-create-delete.integration.test.ts:Test 3 is the one that reproduces the desktop symptom at the host level, and it fails independently of whether the branch is cleaned up.
Suggested fix direction
isNull(workspaces.archivedAt)tofindExistingWorkspaceByBranch(workspaces.ts:161-168) so it matchesadoptExistingWorktree's existing convention. This is the smallest, highest-value change: it stops the app from routing users to a deleted workspace id.workspace-cleanup.ts:514-532, whendeleteBranchis false (or the delete is skipped), probe the ref and push a warning naming it — thebranch --listcheck insidegitDeleteBranchTaskis already the right primitive to factor out. That alone satisfies the "at minimum" ask and makes the CLI's emptywarningsarray honest.--delete-branchtopackages/cli/src/commands/workspaces/delete/command.tsand thread it throughworkspace.delete's input (defaulting tofalseto preserve the current contract) intodestroyWorkspace.workspaceCleanup.inspectalready computeshasUnpushedCommits(workspace-cleanup.ts:123-136), so an auto-delete could be gated on it — though note step 5 deliberately uses-Dtoday because the checkbox is the consent, so any auto-delete should be a distinct policy rather than a change to that flag's semantics.fix(host-service): don't reuse a tombstoned workspace on same-branch …
fix(host-service,cli): release a workspace's git branch on delete so …
fix(host-service,cli): release a workspace's git branch on delete so …
fix(host-service,cli): release a workspace's git branch on delete so …
feat(cli): add --delete-branch to workspaces archive