Skip to content

Web: worktree paths remain as separate projects due to sandbox→root collapse cycle when sandboxes includes root #16182

@benoitheinrich

Description

@benoitheinrich

Description

While testing the fix for #14082 (PR: #14287), I found a Web UI bug: when opening a git worktree via the + / “Open project” picker, the worktree directory is persisted as a top-level project entry in the sidebar instead of collapsing under the repo root as a workspace. This persists across reloads.

The backend correctly resolves the worktree to the root project (same project id + worktree), and /project + /global/event include the correct sandboxes mapping, but the UI collapse logic fails when the root directory is also present in sandboxes, creating a self-edge and tripping the cycle guard.

Plugins

None

OpenCode version

1.2.17 (built from branch fix-14082-sidebar-sandbox-sessions, opencode --version shows 0.0.0-fix-14082-sidebar-sandbox-sessions-…)

Steps to reproduce

  1. Start opencode web (or opencode serve + connect from the Web UI).
  2. Open a git repo root directory, e.g.:
    • root: /path/to/repo
  3. Enable “workspaces” for the project in the sidebar.
  4. Create a git worktree from the repo, e.g.:
    • worktree: /path/to/_wt/repo-feature-branch
  5. In the Web UI, click + (Open project / picker) and open the worktree directory /path/to/_wt/repo-feature-branch.
  6. Observe: the sidebar shows separate top-level project entries for both:
    • /path/to/repo
    • /path/to/_wt/repo-feature-branch
      and the worktree entry has workspaces disabled by default.
  7. Reload the page: the worktree still shows as a separate project.

Expected

If the worktree directory belongs to an existing project (backend indicates this via project.sandboxes), the UI should collapse it under the root project and show it as a workspace, not a separate top-level project entry.

Actual

Worktree stays pinned as a separate top-level project, even though the server reports it as a sandbox of the root project.

Evidence / debugging notes

Backend correctly reports root + sandboxes.

GET /project/current with header x-opencode-directory: /path/to/_wt/repo-feature-branch returns:

  • worktree: /path/to/repo
  • sandboxes includes both /path/to/repo and /path/to/_wt/repo-feature-branch.

GET /project returns a project row for /path/to/repo with:

"sandboxes": [
  "/path/to/repo",
  "/path/to/_wt/repo-feature-branch",
  ...
]

/global/event emits project.updated for /path/to/repo with the same sandboxes list (including root + worktrees).

The Web UI collapse logic (in packages/app/src/context/layout.tsx) builds a map sandbox -> project.worktree for all entries in sandboxes. When sandboxes includes the root, it creates a self-edge map[root]=root. Resolving a worktree becomes worktree -> root -> root, hits the cycle guard, and returns the original directory, preventing collapse.

Potential fix

In the UI sandbox→root mapping, skip sandbox === project.worktree (do not create root -> root), so rootFor(worktree) resolves to the root worktree and the existing collapse effect can remove the pinned worktree project entry.

Related

(Additional note: opencode web proxies the UI from app.opencode.ai; the fix is expected to be in packages/app.)

Metadata

Metadata

Assignees

Labels

webRelates to opencode on web / desktop

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions