Skip to content

Web/Desktop directory picker persists every subdirectory of $HOME as a project (v2) #46679

Description

@epicwhale

Description

Opening the directory picker in the Web or Desktop app (the "Open folder" / "Add workspace" picker, which starts at $HOME) permanently writes dozens of unrelated directories into the project table. Every ~/.cache, ~/.cargo, ~/.ssh, ~/Downloads, ~/Music and so on then shows up as a project in the TUI's Open dialog (ctrl+o) and in every other project list.

The picker itself is read-only from the user's point of view. Browsing the tree should not register projects.

Expected: Browsing in the picker writes nothing. Only directories the user opens, or that own a session or worktree, appear as projects.

Actual: Every subdirectory of $HOME that the picker preloaded is a row in project and an entry in every project list. Dotdirectories are included.

Reproduced on Linux with the Web app; the TUI shows the result in the Ctrl + O picker.

Plugins

No response

OpenCode version

v2 beta (v0.0.0-beta-18743)

Steps to reproduce

  1. Start the v2 server and open the Web app (or Desktop).
  2. Open the directory picker so it mounts at $HOME. Let the tree render.
  3. Open the TUI and press ctrl+o.
  4. Look at the Projects category, or run:
SELECT id, worktree FROM project WHERE vcs IS NULL ORDER BY time_updated DESC;

If helpful, I've asked Claude Code to write a root cause:

Four steps, all on `v2` at `fd73a85de4`:

1. `packages/app/src/workspaces/selection/dialog.tsx:155` — on first mount the picker preloads every child directory of the start path with `sdk.api.file.list({ location: { directory: absolute } })`, one request per child directory.
2. `packages/protocol/src/api.ts:170` — `FileSystemGroup` runs under `locationMiddleware`, so each of those requests boots a `Location` for the probed directory.
3. `packages/core/src/location.ts:24` — booting a `Location` calls `Project.resolve(ref.directory)`.
4. `packages/core/src/project.ts:357` — a directory without a `.git` or `.hg` marker falls through to `persist({ id: Hash.fast("directory:" + directory), vcs: undefined })`, which is an unconditional `upsertProject`. This branch was added in #45107 ("add directory projects", `9361117504`).

There is no read-only path through `Project.resolve`. The existing `discovery: false` option only skips the config and plugin scan; it does not skip the write.

`Project.list()` (`packages/core/src/project.ts:221`) is a bare `SELECT * FROM project`, and the TUI Open dialog (`packages/tui/src/component/dialog-open.tsx:142`) shows every row except `/` and duplicates. So each probed directory becomes a visible project.

Screenshot and/or share link

No response

Operating System

Linux (ubuntu/debian)

Terminal

NA

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions