Repository navigation
Description
Description
After upgrading 1.18.32 → 2.0.12 (curl installer, same ~/.local/share/opencode/opencode.db), /sessions in a non-git directory shows none of that directory's V1 history. Switching the picker to "Show all project sessions" shows them, so the rows were imported — they just aren't attributed to the directory's project anymore.
Root cause, checked against the v2 branch and my DB:
- In V1, sessions from non-git directories live under the catch-all project
global(worktree/), withsession.pathrelative to/(e.g.Users/me/notes). The V1 picker queried{path: relative(worktree, cwd)}against that sameglobalproject, so they matched. - V2
Project.resolvegives every directory a real project; non-git dirs getID.make(Hash.fast("directory:" + dir))(packages/core/src/project.ts).sha1("directory:/Users/me/notes")equals theproject.idV2 created on first launch, byte for byte. - The V1→V2 importer copies
project_idverbatim and only falls back toglobalwhen the id is missing; it never re-resolvesdirectory(packages/core/src/database/v1-migration.bun.ts:586):const projectID = projects.has(nextID.project_id) ? nextID.project_id : Project.ID.global—directoryandpathgo through theINSERT ... SELECTunchanged. - The per-directory picker sends
{project: <real id>, subpath}andSessionStore.listfilterseq(SessionTable.project_id, input.project)(packages/core/src/session/store.ts:107). Rows still taggedglobalcan't match. Theglobal → {directory}fallback indialog-session-list.tsxis unreachable because the current project is no longerglobal.
V1's Project.fromDirectory had a claim step (UPDATE session SET project_id = ? WHERE project_id = 'global' AND directory = ?), but it only ran when a non-global project was resolved — i.e. git repos. V2's project.ts touches only ProjectTable, so nothing ever re-homes these rows.
On my machine: of ~1340 imported V1 sessions, 748 still have project_id = 'global' in session_v2, spread over a dozen directories V1 had filed under global (home dir, a notes directory, non-git work dirs). All 748 are visible only in "all projects" mode.
Check your own DB: SELECT directory, COUNT(*) FROM session_v2 WHERE project_id = 'global' GROUP BY directory ORDER BY 2 DESC;
Possible fixes:
- Importer: for
project_id = 'global'rows, rundirectorythroughProject.resolve, assign that id (creating the project row if needed) and rewritepathrelative to the new root. - Or port the V1 claim step into V2 project persistence: when a directory-hash project is first persisted, adopt
globalsessions with that exactdirectory. - Or picker fallback: for a non-global current project, also match
project_id = 'global' AND directory = <cwd>.
Related, not identical: #42230 (same symptom via /new after git init), #21230 (V1 claim step only matching exact directory, closed "cannot replicate"), #16579 (1.2.21-era ancestor of the worktree=/ mismatch), #38529 (non-git dirs sharing global), #49641 (importer is one-shot; the KV-reset hazard discussed there applies to any re-run-based fix).
Plugins
None relevant — the behaviour is in the core importer and picker SQL cited above.
OpenCode version
2.0.12, upgraded from 1.18.32 via curl -fsSL https://opencode.ai/v2/install | bash (shared opencode.db)
Steps to reproduce
- On 1.18.32, run
opencodein a directory that is not a git repo; create a few sessions. - Install 2.0.x with the curl installer against the same
opencode.db; let the V1→V2 migration finish. - Run
opencodein that directory, open/sessionsin "current directory" mode → no history. - Toggle "Show all project sessions" → the sessions are there.
sqlite3 ~/.local/share/opencode/opencode.db "SELECT project_id, path FROM session_v2 WHERE directory = '<dir>'"→global | Users/...;SELECT id FROM project WHERE worktree = '<dir>'returns the new sha1-based id.
Operating System
macOS 26.1 (Apple Silicon)
Terminal
Reproduced in the TUI and in the Desktop app (1.18.30); the behaviour is server-side.
Activity
github-actions commentedon Sep 22, 2026
This issue might be a duplicate of existing issues. Please check:
project_id='global'and become invisible in the per-directory session picker after upgrading to V2. That issue also includes a workaround (POST /api/session/{sessionID}/move) and confirms the bug is in the V1→V2 importer not re-keyingglobalsessions to their directory's V2 project.Your analysis and reproduction steps are more detailed and add useful depth (especially the
v1-migration.bun.ts:586reference and the DB query), so this issue may still be worth keeping open as a more complete report, but please coordinate with the linked issue.VillagerTom commentedon Sep 24, 2026
Same root cause — with a data point your analysis was missing: not all v1 sessions are affected.
Ran the same drill on my DB (opencode 2.0.14, imported from 1.18.x):
projecttable,pathalready relative to the repo worktree).project_id='global'in v1 itself (tmp/opencode,home/tom/..., media mounts) — and they were never reassigned: the importer's fallback atv1-migration.bun.ts:586only fires when the id is missing (and logsReassigned V1 session with missing project), but the literal'global'row exists inproject(worktree/), soprojects.has('global')istrueand the ternary keeps'global'verbatim. Conclusion: this is a semantic mismatch between v1 and v2 resolution, not the fallback branch.Two additions beyond what's listed here:
'global'too. Cross-referencing theglobalrows from your query against the filesystem: some of those directories are git repos today (the repo may even post-date the sessions), yet their imported rows still carry'global'+ a root-relativepath. The discriminator is the v1 row'sproject_id='global', not the current.gitpresence — V1's claim step only ran for repos it recognized.20260428004200_add_session_pathschema change havepath IS NULL(90 rows in our DB, 2026-04-03→05-05). They keep the correct project id but are still invisible —SessionStore.list's exacteq(path, subpath)(packages/core/src/session/store.ts:108) excludesNULLwhensubpath=''. Sister symptom to migrate: V1-era session exists only in legacy table, invisible to V2 clients #50980, but the rows do exist insession_v2; onlypathis null.Agree your three candidate fixes stand; my vote is the fix 1 (importer rewrite) plus the
isNull+directorylist fallback as the cheap safety net.Hpkings777 commentedon Oct 11, 2026
Confirming this still reproduces on v2.0.26 (latest) — adding fresh, fully-verified evidence.
Environment
@tarquinen/opencode-dcp,@cortexkit/opencode-magic-context,opencode-models-discovery(not required to reproduce)Symptom
From
/root(non-git dir), the directory-scoped TUI session picker (Ctrl+XthenL, or/sessions) shows 1 session. Switching to "all projects" (ctrl+a) shows all of them. 44 legacy V1 sessions are silently hidden.Database evidence (
~/.local/share/opencode/opencode.db)session_v2: 197 rows, of which 175 keepproject_id='global'./root: 45 rows — 44 withproject_id='global'+ 1 new V2 session with the freshly-created projectcd31f2124f23b5c0556ebda23ab6bff6bdc91712.sessiontable has the same distribution → importer copiedproject_idverbatim.globalsessions stranded in/root(44),phorixgpa(42+11),samtron(33),exp(8), … — i.e. every non-git directory.Hash formula verified locally
sha1("directory:" + dir)reproduces the V2 project id byte-for-byte:This matches the
Project.resolveanalysis in #50551.API evidence (
opencode api session.list)directory=/rootglobal+ 1 new)project=cd31f212…project=cd31f212…&subpath=rootWhy the TUI misses them
The client resolves
/rootto its own V2 project (cd31f212…) instead of the legacyglobalproject, so it scopes the query to{ project: "cd31f212…", subpath: "" }→ 1 hit, while the 44 legacy rows live under{ project: "global", subpath: "root" }. The V1→V2 importer created the per-directory project rows but never re-homed theglobalsessions.Workaround
POST /api/session/{sessionID}/movewith the session's owndirectoryre-resolvesproject_idand restores visibility (consistent with #51176 / #50800).Cross-refs: #50551 (code-level cause), #51176, #50800, #54521. Please keep this open until the importer re-keys
globalsessions bydirectory(or the list falls back to directory matching).michalholes commentedon Oct 13, 2026
Additional finding on 2.0.26: fixing
project_idalone is not enough —pathmust be normalized tooReproduced on 2.0.26 (latest, npm
@opencode/cli, Raspberry Pi arm64). Same symptom as the OP, but with an important nuance: re-attributingproject_iddoes not make the sessions visible.pathmust be normalized as well.My case
V1 sessions created in three non-git directories lived under
global(worktree/) withpath= stripped absolute dir (e.g.home/pi,mnt/backup/docker/azcli). After upgrading to 2.0.26, V2 created real projects for those directories on first launch. I manually re-attributed the sessions (movedproject_idfromglobalto the new project hash). They were still hidden.Captured TUI query (strace)
The
/sessionsdialog issues:The empty
subpath=(="exactly the project root") is the filter that excludes them:path'home/pi'(relative to old/worktree)'mnt/backup/docker/azcli'''(empty string)NULLFix that worked
Note:
''(empty string), notNULL. New V2 sessions store''at the project root;NULLdoes not match thesubpath=filter either. I verified this: settingNULLstill returned 0, setting''returned all sessions.Applied to 3 projects (58 sessions total):
/home/pi(41),/mnt/backup/docker/azcli(19),/mnt/backup/docker/hermes(5). All became visible in the TUI picker immediately.Implication for the fix
Any migration/re-attribution fix must handle both:
project_id(the OP's point), andpathas relative to the new project worktree — normalizing the old/-relative stripped path to''when the new worktree equals the session directory.Otherwise users who manually move sessions (or any future tool doing so) will still see them hidden.
Environment
rekram1-node commentedon Oct 21, 2026
Same issue as #49827 (non-git V1 sessions stay under
globaland are hidden from project lists), which has more activity and the open fix (#54048). Tracking there.