Skip to content

fix(host-service): adopt existing worktree at any path on workspace.create#4096

Merged
Kitenite merged 4 commits into
mainfrom
fix-worktree-adoption
May 5, 2026
Merged

fix(host-service): adopt existing worktree at any path on workspace.create#4096
Kitenite merged 4 commits into
mainfrom
fix-worktree-adoption

Conversation

@Kitenite
Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite commented May 5, 2026

Summary

  • Look up the typed branch in listWorktreeBranches and adopt whatever path git already has registered, instead of only matching the canonical ~/.superset/worktrees/<projectId>/<branch> location.
  • Fixes a crash where workspaces.create called git worktree add and hit fatal: '<branch>' is already used by worktree at ..., locking the user out of re-entering their own work when the worktree lived elsewhere (prior session, created outside Superset, or moved in place).
  • Adds an integration test that pre-creates a worktree at a non-canonical path and asserts create() adopts it (persisted worktreePath matches the existing location).

Test plan

  • bun test packages/host-service/test/integration/workspace-create-delete.integration.test.ts
  • Manually: register a worktree for branch foo outside ~/.superset/worktrees, then create a workspace targeting foo from the desktop app — should adopt without error.

Summary by cubic

Adopts existing Git worktrees for a branch at any path during workspace creation and PR checkout, avoiding conflicts and letting users re-enter existing work. Also handles stale (prunable) worktrees so new checkouts succeed.

  • Bug Fixes

    • In workspaces.create and PR checkout, look up the branch via listWorktreeBranches and adopt the registered path; if the local branch OID differs from the PR, return a clear CONFLICT with cleanup hints.
    • Filter prunable worktrees and run git worktree prune before adds; added integration tests for non-canonical adoption and stale-entry pruning.
  • Refactors

    • Unified adoption flow in adoptExistingWorktree, used by both workspaces.create and workspaceCreation.adopt, with relink-by-branch/path and stale-row cleanup.

Written for commit af2cd5a. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Workspace creation now reuses existing on-disk worktrees for matching branches (including PRs) instead of always creating new ones.
  • Bug Fixes

    • Stale worktrees are pruned up front to avoid later worktree-add failures and reduce conflict cases during workspace creation.
  • Tests

    • Added integration tests covering adoption of non-canonical worktree paths and handling of stale/deleted worktree directories.

…reate

When the user types a branch that already has a worktree registered
outside the canonical `~/.superset/worktrees/<projectId>/<branch>` path,
`workspaces.create` would call `git worktree add` and crash with
`fatal: '<branch>' is already used by worktree at ...`, blocking the
user from re-entering their own work. Look up the branch in
`listWorktreeBranches` and adopt whatever path git already knows about.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 5, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

Workspaces creation now prunes stale git worktrees, uses map-based worktree discovery (listWorktreeBranches) to adopt existing worktrees, centralizes adoption logic in adoptExistingWorktree, and excludes prunable worktrees from branch listings. Integration tests cover non-canonical adoption and stale-worktree prune/recreate behavior.

Changes

Worktree adoption and creation flow

Layer / File(s) Summary
Data Shape / Types
packages/host-service/src/trpc/router/workspace-creation/shared/adopt-existing-worktree.ts
Adds exported types: AdoptedWorkspace, AdoptExistingWorktreeArgs, AdoptExistingWorktreeResult.
Core Implementation
packages/host-service/src/trpc/router/workspace-creation/shared/adopt-existing-worktree.ts
Implements adoptExistingWorktree to centralize adoption: handles relink by existing workspace id, reuse by (project,branch)+path, relink-by-path (branch mismatch), or create-new cloud workspace; records base-branch config; handles local DB upsert and cloud rollback on failure.
Branch Discovery Change
packages/host-service/src/trpc/router/workspace-creation/shared/branch-search.ts
listWorktreeBranches now skips wt.prunable entries so prunable worktrees are excluded from checked-out branch lists and adoption targets.
Router / Wiring
packages/host-service/src/trpc/router/workspaces/workspaces.ts, packages/host-service/src/trpc/router/workspace-creation/procedures/adopt.ts
Runs git worktree prune before create; replaces in-procedure adoption checks with listWorktreeBranches lookups and calls into adoptExistingWorktree for PR and typed-branch create flows; adoption messages include discovered worktree paths.
Tests / Integration
packages/host-service/test/integration/workspace-create-delete.integration.test.ts
Adds tests: adoption of an existing non-canonical worktree path; prune/recreate path when stale directory removed; asserts persisted worktreePath and on-disk existence.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Router
  participant Git as GitClient
  participant HostAPI as Host/Cloud API
  participant DB

  Client->>Router: workspaces.create(...)
  Router->>Git: git worktree prune
  Router->>Git: listWorktreeBranches()
  alt existing worktree path found
    Router->>Router: call adoptExistingWorktree(args, hostPromise)
    Router->>HostAPI: host.ensure (hostPromise resolves)
    adoptExistingWorktree->>HostAPI: v2Workspace.getFromHost / create
    adoptExistingWorktree->>DB: delete local conflicts / upsert local workspace
    adoptExistingWorktree->>Git: git config write (record baseBranch)
    adoptExistingWorktree-->>Router: return workspace (alreadyExists?)
  else no existing path
    Router->>Git: git worktree add (create fresh)
    Router->>HostAPI: create cloud workspace
    Router->>DB: register local workspace
    Router-->>Client: created workspace
  end
  Router-->>Client: workspace result
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

"I hopped through branches and paths so sly,
Found prunable leaves and let them lie.
I mapped each root and adopted a tree,
Reclaimed lost paths for you and me.
🐇🌿"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: enabling worktree adoption at any path during workspace creation, addressing the core bug fix.
Description check ✅ Passed The description includes a clear summary, test plan, and related context, though some template sections like 'Related Issues' and 'Type of Change' are not explicitly filled.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-worktree-adoption

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 5, 2026

Greptile Summary

The PR fixes a crash in workspaces.create where git worktree add would fail with fatal: '<branch>' is already used by worktree at … when the existing worktree lived outside the canonical path. The fix replaces the single-path lookup with a full listWorktreeBranches map scan. One P2 edge case noted: stale/prunable worktrees are not excluded from the adoption map.

Confidence Score: 4/5

Safe to merge; the core fix is correct and well-tested; one P2 edge case around prunable worktrees is worth a follow-up.

Only P2 findings — the stale/prunable worktree edge case is a narrow regression that requires a previously-deleted-but-not-pruned worktree to exist for the target branch. No P0/P1 issues found.

packages/host-service/src/trpc/router/workspaces/workspaces.ts — specifically the prunable-worktree gap in the adoption map lookup.

Important Files Changed

Filename Overview
packages/host-service/src/trpc/router/workspaces/workspaces.ts Adoption logic updated to look up the existing worktree path via listWorktreeBranches for any registered location instead of only the canonical path; stale/prunable entries are not excluded from the map.
packages/host-service/test/integration/workspace-create-delete.integration.test.ts New integration test pre-creates a worktree at a non-canonical path and verifies that workspaces.create adopts that exact path instead of failing with git worktree add.
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
packages/host-service/src/trpc/router/workspaces/workspaces.ts:719-727
**Stale/prunable worktrees can be adopted as `worktreePath`**

`listWorktreeBranches` iterates all worktrees — including ones marked `prunable` (directory deleted without `git worktree remove`). When such a stale entry exists for `resolvedBranch`, `existingWorktreePath` is set to the now-missing path, `adopted = true` skips `git worktree add`, and the subsequent `enablePushAutoSetupRemote(git, worktreePath, …)` runs against a directory that no longer exists, likely throwing. Before this PR the same risk existed only at the canonical path; now it applies to any registered path. Filtering `wt.prunable != null` entries in `listWorktreeBranches` (or inline here) would prevent this.

Reviews (1): Last reviewed commit: "fix(host-service): adopt existing worktr..." | Re-trigger Greptile

Comment on lines +719 to +727
const existingWorktreePath = typedBranch
? (await listWorktreeBranches(git)).worktreeMap.get(
resolvedBranch,
)
: undefined;
const adopted = !!existingWorktreePath;
worktreePath =
existingWorktreePath ??
safeResolveWorktreePath(localProject.id, resolvedBranch);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Stale/prunable worktrees can be adopted as worktreePath

listWorktreeBranches iterates all worktrees — including ones marked prunable (directory deleted without git worktree remove). When such a stale entry exists for resolvedBranch, existingWorktreePath is set to the now-missing path, adopted = true skips git worktree add, and the subsequent enablePushAutoSetupRemote(git, worktreePath, …) runs against a directory that no longer exists, likely throwing. Before this PR the same risk existed only at the canonical path; now it applies to any registered path. Filtering wt.prunable != null entries in listWorktreeBranches (or inline here) would prevent this.

Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/host-service/src/trpc/router/workspaces/workspaces.ts
Line: 719-727

Comment:
**Stale/prunable worktrees can be adopted as `worktreePath`**

`listWorktreeBranches` iterates all worktrees — including ones marked `prunable` (directory deleted without `git worktree remove`). When such a stale entry exists for `resolvedBranch`, `existingWorktreePath` is set to the now-missing path, `adopted = true` skips `git worktree add`, and the subsequent `enablePushAutoSetupRemote(git, worktreePath, …)` runs against a directory that no longer exists, likely throwing. Before this PR the same risk existed only at the canonical path; now it applies to any registered path. Filtering `wt.prunable != null` entries in `listWorktreeBranches` (or inline here) would prevent this.

How can I resolve this? If you propose a fix, please make it concise.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 5, 2026

🚀 Preview Deployment

🔗 Preview Links

Service Status Link
Neon Database (Neon) View Branch
Vercel API (Vercel) Open Preview
Vercel Web (Vercel) Open Preview
Vercel Marketing (Vercel) Open Preview
Vercel Admin (Vercel) Open Preview
Vercel Docs (Vercel) Open Preview

Preview updates automatically with new commits

Kitenite added 3 commits May 5, 2026 11:30
…lper

`workspaces.create` and `workspaceCreation.adopt` had diverged copies of
the post-worktreePath registration flow — the former missing stale-row
hygiene, the latter only reachable from v1→v2 migration. Extract the
shared invariants (relink by branch, relink-on-rename by path, stale-row
cleanup, fresh cloud+local registration) into `adoptExistingWorktree`
and call it from both procedures.

Also fixes the PR-creation path: when a PR's local branch is already in
a worktree at any path, `git worktree add` would refuse. Look the branch
up in the worktree map first; on OID match adopt via the helper, on OID
mismatch surface a CONFLICT that points at `git worktree remove` before
`git branch -D`.
`git worktree list` keeps reporting an entry after its directory is
deleted (marked `prunable`), and git still claims the branch until
prune runs. The previous fix would happily set `worktreePath` to the
missing dir; the original canonical-path check had the same bug, just
narrower. Two changes:

- `listWorktreeBranches` filters prunable entries so adopt callers
  never target a stale path.
- `workspaces.create` runs `git worktree prune` up front so the fresh
  `git worktree add` at the canonical path can succeed without `branch
  is already used by worktree at <missing-path>`.
Compress JSDoc and rationale comments around the new adoption helper
and `workspaces.create` prune step. Same intent, fewer lines.
@Kitenite Kitenite merged commit 507395b into main May 5, 2026
15 of 16 checks passed
@Kitenite Kitenite deleted the fix-worktree-adoption branch May 5, 2026 20:08
@saddlepaddle saddlepaddle mentioned this pull request May 5, 2026
3 tasks
saddlepaddle added a commit that referenced this pull request May 5, 2026
Changes since v0.2.8:

- `superset agents list` (and demoted "presets" to a UI-only concept).
  CLI now reads canonical agents from host-service. (#4097)
- Host-service: refresh stale OAuth access tokens on remote workspace
  ops instead of failing the request. (#4106)
- Host-service: workspace.create now adopts an existing worktree at any
  path, not just the canonical one. (#4096)
- Host-service: AI workspace naming times out after 5s and falls back
  to a deterministic name. (#4102)

Push cli-v0.2.9 after this lands to fire the release pipeline.
saddlepaddle pushed a commit that referenced this pull request May 6, 2026
…reate (#4096)

* fix(host-service): adopt existing worktree at any path on workspace.create

When the user types a branch that already has a worktree registered
outside the canonical `~/.superset/worktrees/<projectId>/<branch>` path,
`workspaces.create` would call `git worktree add` and crash with
`fatal: '<branch>' is already used by worktree at ...`, blocking the
user from re-entering their own work. Look up the branch in
`listWorktreeBranches` and adopt whatever path git already knows about.

* refactor(host-service): unify workspace adoption behind one shared helper

`workspaces.create` and `workspaceCreation.adopt` had diverged copies of
the post-worktreePath registration flow — the former missing stale-row
hygiene, the latter only reachable from v1→v2 migration. Extract the
shared invariants (relink by branch, relink-on-rename by path, stale-row
cleanup, fresh cloud+local registration) into `adoptExistingWorktree`
and call it from both procedures.

Also fixes the PR-creation path: when a PR's local branch is already in
a worktree at any path, `git worktree add` would refuse. Look the branch
up in the worktree map first; on OID match adopt via the helper, on OID
mismatch surface a CONFLICT that points at `git worktree remove` before
`git branch -D`.

* fix(host-service): handle prunable worktrees in workspaces.create

`git worktree list` keeps reporting an entry after its directory is
deleted (marked `prunable`), and git still claims the branch until
prune runs. The previous fix would happily set `worktreePath` to the
missing dir; the original canonical-path check had the same bug, just
narrower. Two changes:

- `listWorktreeBranches` filters prunable entries so adopt callers
  never target a stale path.
- `workspaces.create` runs `git worktree prune` up front so the fresh
  `git worktree add` at the canonical path can succeed without `branch
  is already used by worktree at <missing-path>`.

* chore(host-service): tighten comments in worktree-adoption code

Compress JSDoc and rationale comments around the new adoption helper
and `workspaces.create` prune step. Same intent, fewer lines.
saddlepaddle added a commit that referenced this pull request May 6, 2026
Changes since v0.2.8:

- `superset agents list` (and demoted "presets" to a UI-only concept).
  CLI now reads canonical agents from host-service. (#4097)
- Host-service: refresh stale OAuth access tokens on remote workspace
  ops instead of failing the request. (#4106)
- Host-service: workspace.create now adopts an existing worktree at any
  path, not just the canonical one. (#4096)
- Host-service: AI workspace naming times out after 5s and falls back
  to a deterministic name. (#4102)

Push cli-v0.2.9 after this lands to fire the release pipeline.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant