Repository navigation
Description
Summary
Sessions are missing from the folder-scoped session list ("current directory") even though they appear in "all sessions" and their location.directory is that folder. A worktree can own several project rows, and GET /api/session?directory=… resolves the directory to only one of them, so sessions filed under the other cell are invisible. Additionally, GET /api/location on the shared service returns the service's start directory instead of the caller's cwd, so any client that relies on the default location gets the wrong project.
Environment
- opencode version:
opencode v2.1.4 - OS: Linux
7.0.0-38-generic #38-Ubuntu SMP PREEMPT_DYNAMIC Fri Sep 4 09:10:14 UTC 2026 x86_64 GNU/Linux(Kubuntu 26.04) - Terminal:
TERM=xterm-256color,COLORTERM=truecolor - Shell:
/bin/bash - Install/channel: release
latest,~/.opencode/bin/opencode - Active plugins: local plugin file
~/.config/opencode/plugins/git-guardian.ts(autogit init+ checkpoint). Not referenced fromopencode.jsonc; loaded by file discovery.
All reproductions below were done with opencode api only — no TUI involved — against the shared background service (opencode service status → http://127.0.0.1:49374).
Reproduction
A. Duplicate project rows per worktree split the folder listing
- Inspect project rows; several worktrees own two cells:
$ sqlite3 ~/.local/share/opencode/opencode.db \
"select worktree, id, time_created from project group by worktree having count(*) > 1"
~/projects/ai_pipelines/security_demonstration|52df08db52efd8abf1c47a3204bc0e1dae147da9|1787915000206
~/projects/ai_pipelines/security_demonstration|cbfb80bfa0efd33674e83daa9ef372fd573977fe|1788335455295
~/projects/ai_pipelines/cyrillic_handwriting_recognizer|61027ee44b9b4f7fdf587e4bb8326e9e1277b941|1790580137773
~/projects/ai_pipelines/cyrillic_handwriting_recognizer|d9bb6dc657a81a4e9558328a3d46d96a506b7306|1793029087177(4 duplicated worktrees of 34 present: also ~/playground, ~/projects)
- Evict the directory's location and list sessions by directory:
$ opencode api delete '/api/debug/location?directory=~/projects/ai_pipelines/security_demonstration'
$ opencode api get '/api/session?directory=~/projects/ai_pipelines/security_demonstration&limit=100'
# -> 14 sessions, every one projectID=52df08db…- Now load the same location and repeat:
$ opencode api get '/api/location?location[directory]=~/projects/ai_pipelines/security_demonstration'
# -> project.id = cbfb80bf… (a DIFFERENT cell than step 2 used)
$ opencode api get '/api/session?directory=~/projects/ai_pipelines/security_demonstration&limit=100'
# -> 15 sessions (both cells)So the directory→project resolution differs between the two code paths, and the newer
cell cbfb80bf… holds sessions that the older cell's listing never returns.
For the other folder the UI effect was: 2 sessions visible via "all sessions", only 0–1 in
the folder view, until each session was re-tagged with
POST /api/session/{id}/move {"directory": "<same directory>"}.
B. GET /api/location ignores the caller's cwd on the shared service
$ cd ~/projects/main/BigBikeData && opencode api get /api/location
$ cd ~/projects/ai_pipelines/english_tutor && opencode api get /api/location
$ cd ~/projects/ai_pipelines/…_recognizer && opencode api get /api/location
# all three:
{"directory":"/home/user","project":{"id":"fdd7cfd6…","canonical":"/home/user"}}~ here is the home directory, where the background service happened to start.
GET /api/debug/location proves all of those directories are loaded locations
([{"directory":…/playground"},{"directory":…/…_recognizer"},…]), yet /api/location
keeps returning ~. POST /api/location/reload does not change it.
A private instance started from the same cwd resolves correctly
(opencode --standalone <dir> shows repo:main in the status bar), so the shared
service is the trigger.
The explicit form works, which is the workaround:
$ opencode api get '/api/location?location[directory]=~/projects/ai_pipelines/english_tutor'
{"directory":"…/english_tutor","project":{"id":"6d97d10e…", …}}C. Secondary: global (worktree /) sessions never list by their own directory
project_id='global' has 73 session_v2 rows carrying a real session_v2.directory
(e.g. ~/App, ~/mega/dotfiles, 31 rows with /home/user).
GET /api/session?directory=<their directory> returns none of them.
Related: scoping is ancestry-based, so with location ~,
GET /api/session?directory=~ returned 32 sessions — 31 of them global sessions whose
real directory is a subfolder. An ad-hoc session started in ~ pollutes the ~ list.
Expected Behavior
- A worktree resolves to exactly one project cell (or the listing ignores cells and
selects onsession_v2.directory, which is stored per session), so a folder's list shows
every session whoselocation.directoryis that folder. GET /api/locationwithout an explicitlocationparam should resolve the
requesting client's working directory, not the directory the background service was
started in.project_directoryalready has exactly one row per worktree in a healthy DB
(project_id+directoryPK), so it looks like the intended source of truth.
Additional Context (data)
Numbers from this machine at time of writing: 195 session_v2 rows, 97 legacy session
rows (they diverge per project: e.g. one project has 6 legacy rows vs 12 v2 rows, several
have v2 rows and 0 legacy rows). If listing mixes both tables depending on location, that
could add to the "vanishing" effect.
Data note on how the duplicates likely appeared: ~/projects became a git worktree on
2026-10-26 11:44 (chore(init): initial snapshot, i.e. an auto-checkpoint), i.e. a new
project root appeared above existing projects mid-flight, and the dated project
rows above show cells being created again for paths that already had one.
Additional Context
- Reproducible consistently (both A and B, repeated several times today).
- Workaround in use: always start the TUI with an explicit directory
(opencode <dir>), and re-tag orphaned sessions with
POST /api/session/{id}/move {"directory":"<their directory>"}— this restores the
folder listing immediately, confirming the session data itself is intact. - No relation to the sessions' content: affected sessions were created in the correct
folder; only theirproject_idcell differs. - Happy to attach a redacted
sqlite3dump ofproject,project_directoryand
session_v2(id, project_id, directory)if that helps.
Activity
opencode-agent commentedon Oct 26, 2026
Thanks for the detailed report. I was able to reproduce the main problem (part A) on 2.1.4, and it still happens on the latest
dev(0f3a0c206).I used one way of getting a second
projectrow for the same worktree, adding a git remote (the trigger from #57985):git inita fresh repo with an empty root commit, then create a session withopencode api session.create.opencode session listshows it.git remote add origin …. The samesession listin that directory now returns nothing.The database then has two
projectrows with the same worktree. Both sessions have the samedirectorybut differentproject_id, and the folder-scoped list only returns sessions from one of them. The script below reproduces it.I did not reproduce your exact trigger (a parent folder becoming a git repo), the different results before and after the location is evicted, or parts B and C.
Related issues:
global-project sessions hidden from folder lists (your part C)Reproduction script (repro.sh)
Run it:
bash repro.shpathexplorer commentedon Oct 26, 2026
Repro for Part B (location ignores caller's cwd)
Confirmed in a fresh isolated environment (
XDG_*+opencode serve --port 45991):All three calls return the directory where the service was started (
/tmp/opencode-repro), not the caller's cwd.Explicit param works:
/api/debug/locationproves the other dirs are loaded:[{"directory":"/tmp/opencode-repro"},{"directory":"/tmp/…/proj3"}]Repro script (
repro-B.sh):This is independent of the duplicate-
project-row issue (Part A) — it reproduces with a single project per directory and no sessions created at all.argszero commentedon Oct 27, 2026
I'd like to work on this.
I proved part A at the store layer without needing the TUI. In
packages/core/test/session-store.test.ts, with twoprojectrows sharing one worktree (proj_old,proj_new) plus one row on a different worktree, and one session filed under each,SessionStore.list({ project: proj_new })returns onlyses_newon currentdev(bc175f675c) — the sibling row's session is missing. The store filters onproject_idalone:That is the whole of part A:
Session.createpinsprojectIDto whichever rowProject.resolvereturned at creation time, so once a worktree owns two rows — the git-remote fingerprint change from #57985 — a folder-scoped list can only ever see the subset filed under the row it resolved to.My intended fix, kept to the located site: a project-scoped list should cover every
projectrow that shares the worktree, since those rows are the same folder to the caller. That leaves the API, the cursor semantics and the session rows untouched, and it also repairs existing sessions, which a change that only stops new duplicates would not.Scope: part A only. Part B (
/api/locationreturning the service's start directory instead of the caller's cwd) looks like a separate defect in the location middleware, and I would rather not fold it into the same change.fix(core): list sessions from every project row of a worktree