Repository navigation
Description
Summary
On Windows, every fresh start of the OpenCode Desktop background service (opencode-cli.exe serve --service) registers the user's home directory (C:\Users\<user>) and the drive root (C:\) as projects in the server database. These project rows contain no sessions. Deleting them from Desktop → Settings → Projects does not stick: the next fresh service start (app relaunch, reboot auto-start) re-registers them, so the Projects list keeps growing ghost entries.
Environment
- opencode version: 2.0.5 (desktop-managed CLI, channel
latest) - OS: Windows 11 Pro 24H2 (Windows_NT 10.0.26200, win32 x64)
- Terminal: OpenCode Desktop (Electron) 2.0.5
- Shell: PowerShell 7 (user default)
- Install/channel: desktop app packaged build,
latest - Active plugins:
opencode-mem@latest(global config)
Reproduction
- In Desktop → Settings → Projects, delete the extra entries (keep only the real project).
- Fully quit the Desktop app and kill the background service (
opencode-cli.exe), or reboot (Desktop auto-starts on login). - Open the Desktop app and let the background service start fresh.
- Settings → Projects shows the deleted entries again:
C:\Users\<user>andC:\, both with zero sessions.
Reproducible consistently on every fresh service start.
Expected Behavior
The background service should not register directories as projects on its own. Startup should only register (at most) directories actually opened by the user — not the service's cwd (user home) and not the drive root.
Actual Behavior
Project rows for C:\Users\<user> and C:\ are inserted into the project table on service boot, matching the service startup timeline exactly (same second):
project table (opencode.db, redacted):
| worktree | time_created | note |
|---|---|---|
C:/Users/<user>/Documents/Default Project |
earlier | real project |
C:/Users/<user> |
01:27:14 local | ghost, 0 sessions |
C:/ |
01:27:20 local | ghost, 0 sessions, 6 s after boot |
/ |
built-in | global project |
Corresponding server log (~/.local/share/opencode/log/opencode.log, timestamps UTC):
2026-09-16T17:27:11Z run=4acc5529 message="cli starting" version=2.0.5 args=["--version"] role=cli
2026-09-16T17:27:12Z run=939abf0f message="cli starting" version=2.0.5 args=["serve","--service"] role=cli
2026-09-16T17:27:14Z run=939abf0f message="location services booted" directory="C:\Users\<user>" role=server
2026-09-16T17:27:20Z run=939abf0f message="watcher subscribe" path="C:\" type=entries role=server
Desktop main.log (01:27 boot) shows the app auto-starting and spawning the v2 background service; no explicit project directory is passed.
session / session_v2 tables contain no rows for either ghost directory — the projects exist purely from startup registration.
Additional Context
- The ghost rows also cause the service to set up
type=entriesdirectory watchers onC:\,C:\Users, andC:\Users\<user>(drive-root-level watchers), which may have performance implications on machines with busy root directories. - Possibly related: fix(server): reject instance requests for non-existent directories #33309 (reject instance requests for non-existent directories), fix(app): show server-known projects in project lists #47208 (show server-known projects in project lists).
- Desktop app:
ai.opencode.desktop2.0.5; service command line:opencode-cli.exe serve --service.
Activity
github-actions commentedon Sep 17, 2026
This issue might be a duplicate of existing issues. Please check:
location services booted directory="C:\Users\<user>"). Your issue adds theC:\drive root registration and the persistence-after-deletion behavior, which look like related symptoms or a progression of the same bug.InstanceStore.booteagerly initializes every registered project directory at startup — overlaps with the ghost-project accumulation and drive-root watcher side effects you observed.If your issue adds meaningfully new information (drive root registration, deletion not sticking), it may be worth keeping open, but please cross-reference #49209 to avoid duplicate fix efforts.
junior-xj97-chan commentedon Sep 29, 2026
Still reproducible on 2.0.19, and I think I now have the exact mechanism plus a captured occurrence. Updating with the decompiled evidence.
1. The fallback
From the shipped bundle (
opencode-cli.exe2.0.19, Windows):Any request that carries neither
location[directory]norx-opencode-directoryis silently attributed to the server process's own cwd. The desktop app spawnsopencode-cli.exe serve --servicewith cwd =C:\Users\<user>(I verified this by readingProcessParameters->CurrentDirectoryout of the live service process PEB, not by inference), so a single directoryless client request resolves to the user's home directory.The same fallback appears in the session handlers:
2. Booting a location writes the project row
The row is a side effect of booting a location, not of a user adding a project. Captured from a fresh occurrence (2026-09-30 01:57 local, service not restarted — same run id throughout):
projectis doing double duty: user-visible projects and internal runtime locations. Once those share a table, "the service happened to touch a directory" becomes "a new item in your project list".3. Why it looks intermittent
time_updated/time_activechange — which is why a manual delete appears to hold for a while.4. A third ghost: opencode's own directories
Twice, one of opencode's own directories got registered:
C:\Users\<user>\.config\opencode(2026-09-28 02:15)C:\Users\<user>\.local\share\opencode(2026-09-29 23:45)For the second one, the Playwright MCP server process spawned for that location still had its cwd set to that directory, which is how I identified it. Same class of problem: any directory that becomes a location becomes a project.
5. Conjecture: why this looks Windows-only
On POSIX the service cwd is typically
/, which is already the built-in global project row, so the fallback is invisible. On Windows the cwd isC:\Users\<user>while global is/, so you get two extra rows instead of none. I have not verified this on macOS/Linux — flagging it only as a possible reason this report looks low-impact.6. Ruled out: plugins
I suspected the global plugin
opencode-memat first. It is not the cause: in v2 mode its client is a local shim (dist/v2/legacy-client.js→createLegacyClient(ctx)) whose methods either return constants or call in-processctx.session.*/ctx.generate.*APIs — it never issues HTTP, so it cannot trigger the fallback.services/user-profile/ai-cleanup.jsshares the same shim viagetV2Client(). I mention it only so nobody repeats that dead end.Impact
Cosmetic plus watcher overhead — no data or session loss. The service also installs recursive
type=entrieswatchers onC:\,C:\UsersandC:\Users\<user>for these rows.Suggested fix
projectrow as a side effect of booting a location; only register when a user explicitly adds a project (or when a session is actually created there).process.cwd().location[directory].Environment
opencode 2.0.19 (desktop
ai.opencode.desktop2.0.19, channellatest), Windows 11 Pro 24H2 x64, PowerShell 7.opencodejr commentedon Oct 28, 2026
Thanks for the detailed report. I can reproduce the home-directory part on 2.0.5, and it still happens on the latest
dev(950111f).What I saw (Linux, server API only, no Desktop):
opencode serve --servicewith the home directory as its working directory registers nothing by itself; theprojecttable stays empty.GET /api/agent) makes the server fall back to its own working directory. It boots location services there (location services booted directory=<home>) and stores a project row for it, with no sessions.The startup scan also watches
/and every parent directory, which fits yourwatcher subscribe path="C:\"log line. I could not get a project row for the drive root itself here, and I couldn't check which request the Desktop app sends at startup, since that needs Windows and Desktop.The script below reproduces it against the service API.
Reproduction script (repro.sh)
Run it:
bash repro.shjunior-xj97-chan commentedon Nov 1, 2026
Still reproducible on 2.2.5 (Desktop 2.2.5, Windows 11,
opencode-cli.exe serve --service), and I can now narrow the "which request" part down to the server side and add the exact endpoints that persist the ghost row.What I can prove
1. The desktop app puts the service's cwd on the home directory.
packages/desktop/src/main/lifecycle/configure.ts:18-21The
serve --serviceprocess is spawned from the main process and inherits that cwd, so the server'sprocess.cwd()isC:\Users\<user>.2. Any location-scoped request without a directory lands on that cwd.
packages/server/src/location.ts:71-793. Booting that location persists a project row.
packages/core/src/location.ts:28-29persistis true whenever the directory exists — so booting a location for the fallback cwd writes aprojectrow for the home directory with zero sessions.Reproduced in isolation (no Desktop, no plugins)
Fresh
XDG_DATA_HOME,servestarted with cwd = a temp "fake home", then exactly one request per fresh database. Endpoints that wroteproject = <cwd>with nolocation[directory]and nox-opencode-directoryheader:GET /api/configGET /api/agentGET /api/commandGET /api/providerGET /api/modelGET /api/skillGET /api/locationGET /api/infoGET /api/projectGET /api/sessionGET /api/event(SSE)So it is not the service booting itself —
servewith an empty database writes nothing. It is the first directoryless client request, and seven different endpoint families are enough to trigger it.How often it hits this machine
~/.local/share/opencode/log/opencode.log(45 MB):location services bootedlines in total.C:\Users\<user>, 21 started with the real project.Temp\opencode19,Temp\ghref19, five short-livedopencode-browser-*temp dirs 5-7 each,C:\14,\9.So the home row is re-created on ~85% of launches, which matches "deletion does not stick". The row only comes back when the home location is booted again; it survives in the DB untouched while that location stays warm, and reappears on the next boot after idle eviction / relaunch.
What I could not pin down
Which renderer call omits the directory. The only no-argument
location.get()in the app is the directory-picker fallback (packages/app/src/workspaces/selection/dialog.tsx:116), but that dialog only mounts when the picker opens, so it is probably not the startup call. Every other call site I found passes{ location: { directory } }.Suggested fix
Two independent guards, either one stops the ghost row:
process.cwd()fallback —requestRef()could return a flag andLocation.layercould callproject.resolve(dir, { persist: false })for it (the option already exists). A directoryless request should not create durable state; it may even deserve a 400.process.chdir(homedir())in the desktop main process, or spawn the service in a neutral cwd (app userData). That alone changes the blast radius from "the user's home shows up as a project" to "a scratch dir nobody notices", but (1) is the real fix for the other directoryless callers you mentioned (plugins/SDK).If you want the exact client request, a debug log line in
requestRef()when the fallback is taken (with the request URL) would surface it in one launch.