Skip to content

removeProject without a hostId can delete the wrong host's project when repo IDs are duplicated across hosts #13071

Closed

Description

@AmethystLiang

Details

Found while investigating #12875. This is a pre-existing bug, independent of the orphaned-workspace fix (docs/orphaned-workspace-removal.md scopes it out).

Scenario: repo ID R exists on host A and host B. Host A's row disappears from the renderer catalog (refresh drop) while its worktrees remain; host B's row is live. The user removes the "host A" project from the sidebar.

What happens: the sidebar remove flow never passes options.hostId:

  • RemoveFolderDialog.tsx:62 — removeProject(repoId, { errorFeedback: 'toast' })
  • WorktreeList.tsx:~6159 — has the full Repo in hand and discards its host
  • delete-worktree-flow.ts:~224 — has target.hostId in hand and drops it

With no hostId, findRepoForHost (repo-host-identity.ts:45-47) uses its unique-candidate fallback: exactly one row with that ID → return it regardless of host. So it returns host B's live row, and removeProject (repos.ts:3291+) proceeds to kill B's PTYs, purge B's worktrees, and drop B's row. Net: the working host-B project is destroyed and the host-A orphan rows survive.

ssh-host-remove-workspaces.ts:45-51 documents this exact hazard and defends against it by passing an explicit hostId; the sidebar flow predates that defense.

Related smaller bugs in the same flow:

  • RemoveFolderDialog.tsx:35 resolves the SSH host label with a bare first-by-id repos.find — can name the wrong host in the confirmation text.
  • delete-worktree-flow.ts:222 uses the same bare find for the display name, while the sidebar header uses last-write-wins getIndexedRepoMap — header and dialog can describe two different rows of the same ID.

Fix sketch: carry hostId in the confirm-remove-folder modal data (both openers already hold it), forward it from the dialog, and make removeProject refuse the unique-candidate/focused-host fallbacks for removals.

Made with Orca 🐋

Activity

  1. added a commit that references this issue on Aug 9, 2026
    1ca23c2
  2. manuaudio commented on Aug 9, 2026

    @manuaudio
    Contributor

    Fix is up in #13117.

    It carries the host through the three sidebar sites you listed, plus a fourth caller with the same omission: the orphaned per-workspace-env purge in worktrees.ts calls removeProject(repoId) bare while it's already iterating full Repo objects, so it lands on the same unique-candidate fallback.

    One thing I deliberately left alone, since you raised it: making removeProject refuse the fallbacks for removals would break group removal. selectProjectGroupRemovalTargets pushes repo.id and drops the host, so removeProject is legitimately called there with nothing to pass — a blanket refusal turns those into silent no-ops and strands the projects. That site wants the host plumbed through ProjectGroupRemovalTargets first, which changes a shared selector's shape, so it felt like its own PR rather than something to smuggle into this one. Happy to pick it up either way.

    Regression test models your scenario directly — host A's row dropped from the catalog, host B's row the only one left under the id — and asserts both the hostId the modal carries and the name in the confirmation, since host B's name showing up is the tell that resolution went to the wrong row. Reverting the plumbing fails it.

    Rebased onto main and re-ran everything just now: lint, typecheck, build and the sidebar/store suites are clean. Worth mentioning that the workflows on it are still parked on "approve and run", so there's no CI signal on the PR yet — if someone can kick that off I'll chase down anything it turns up.

  3. added theissue type on Sep 19, 2026
  4. added a commit that references this issue on Oct 8, 2026
    0ca8ae1
  5. added a commit that references this issue on Oct 8, 2026
    e3639ef
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workinggit

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions