Repository navigation
Description
Summary
Adding a git remote to a repository changes the project ID OpenCode gives it. Every session created before the remote was added stays under the old project ID, so it no longer appears in /sessions or opencode session list for that directory. Nothing is deleted, but the sessions can't be found from the project. I lost sight of 163 sessions (20 top-level) in one repo this way after pushing it to GitHub.
Environment
- opencode version: 2.0.32
- OS: macOS, Darwin 24.6.0 (x86_64)
- Terminal: WarpTerminal (TERM=xterm-256color, COLORTERM=truecolor)
- Shell: /bin/bash
- Install/channel: Homebrew
anomalyco/tap/opencode-v2, stable 2.0.32, running as the background service (opencode serve --service) - Active plugins:
opencode-with-claude@latest(global config), localagentcraft.js(~/.config/opencode/.opencode/plugins). Neither seems related.
Reproduction
--standalone forces a fresh look at the directory. With the shared service the same thing happens the first time it re-resolves the directory, for example after "location services evicted" for an idle directory.
mkdir /tmp/repro && cd /tmp/repro
git init -q && git commit -q --allow-empty -m root
opencode api session.create --standalone --data '{"title":"created before the remote"}'
opencode session list --standalone # lists the session
git remote add origin [email protected]:example/repro.git
opencode session list --standalone # emptyExpected Behavior
A repository's sessions stay with it when a remote is added. Either the project ID stays stable, or existing sessions move to the new ID along with the project.
Actual Behavior
- Before the remote, the project ID is the root commit (
29d1de6…in my repro,project.vcs = git). - After
git remote add, the next resolution creates a secondprojectrow for the same worktree, with a different ID (9c69e8f…), and new sessions go there. - Old sessions keep
session_v2.project_id= the old ID, so the project's session list no longer shows them. They're still in the database and open by ID withopencode -s <id>.
In the real case the log shows the switch at the moment the service re-resolved the directory after an idle eviction:
message="location services evicted" directory=<repo>
message="spawning process" command=git args="[\"rev-parse\",\"--git-dir\",\"--git-common-dir\",\"--show-toplevel\"]"
message="spawning process" command=git args="[\"remote\",\"get-url\",\"origin\"]"
From then on the repo resolved to the new project; the old one kept its 163 sessions and 9 saved permission rules (permission.project_id).
Additional Context
- Reproducible every time with the steps above.
- Probably affects anyone who starts locally and later pushes to GitHub, which is a common path.
- Workaround (unsupported; I backed up the DB first):
update session_v2 set project_id='<new>' where project_id='<old>'and the same forpermission. Sessions reappeared in/sessionsstraight away, without restarting the service.
Activity
opencode-agent commentedon Oct 22, 2026
Thanks for the clear report and repro. I could reproduce this on 2.0.32, and it still happens on the latest
dev(248bfbe11):git initand an empty root commit in a fresh directory.opencode api session.create --standalone ...: the session gets a project ID based on the root commit, andopencode session list --standalonelists it.git remote add origin [email protected]:example/repro.git.opencode session list --standalonenow prints nothing. The repo resolves to a new project ID based on the remote, and the earlier session stays under the old one.The script below reproduces it.
I haven't tested a fix, but the cause looks like this: once a remote exists, the remote-based ID replaces the existing one. The step that moves sessions to a directory's new identity only covers sessions in the
globalproject and in a project ID read from a legacy.git/opencodefile, which v2 never writes. Sessions under the root-commit project aren't moved, and neither are the permission rules. Your SQL workaround is the only way to recover them for now.Reproduction script (repro.sh)
Run it:
bash repro.shloofahss commentedon Oct 23, 2026
Adjacent data point on 2.0.32: when a plain directory becomes a git repo, the carry-over does
work — including for sessions in sub-directories — so whatever strands sessions in the remote case
looks narrower than "only sessions in the
globalproject are moved".Isolated repro (own XDG_DATA_HOME/XDG_CONFIG_HOME, no plugins):
Before
git initthe two sessions get path-based project ids (007f4a82…forparent,5c825dbb…forparent/sub), each listed in its own directory. Aftergit init+ root commit(
cd4c6bb8…), both sessions havesession_v2.project_id = cd4c6bb8…and both are listed fromeither directory. The two old path-based
projectrows are still present (not deleted), so thisdoes not look like the
migrateProjectIdold→new path either.So sessions do follow a directory's new identity even when their old project id is not
global.If that is the same code meant to cover the remote case, what differs there?
One unrelated thing noticed in the same run, in case it is known:
project_directorystays emptyin the repro database — no row for the newly created git project. On the machine I upgraded, the
newest row in that table predates the 2.0.32 upgrade, so nothing appears to write it any more.
holny commentedon Oct 24, 2026
Reproduced the same switch on the latest dev. In packages/core/src/project.ts the resolve path ranks the remote URL ahead of the cached previous ID (remote(repo) ?? previous ?? rootCommit(repo)), so the first resolution after git remote add mints a second project row and strands every session under the root-commit ID.
Happy to take this: I'd flip the order so an already-resolved repo keeps its ID (fresh clones still get the remote-derived one), plus a regression test. PR within a day or two.
argszero commentedon Oct 29, 2026
There is a pull request open for this defect: #59946, which closes #59371 — the same problem reported from the
session listside.What it does:
packages/core/src/session/store.tslists sessions for everyprojectrow that shares the caller's worktree, instead of only the single row it was handed. Your sessions were never deleted; they are filed under the pre-remote row, and the query only ever asked about one of the folder's rows. Matching by worktree makes them visible again for repositories already in this state, not only for ones created after the fix.How it was proven, red before green: with two
projectrows on one worktree and one on another worktree, listing for the newer row returned only its own session before the change, and both sessions after it, while the other worktree's session stayed out. The change also carries a regression test for theglobalrow, which must never be matched: itsworktreeis whichever commitless repository resolved to it first, so matching it cross-listed unrelated directories — that case fails without the exclusion and passes with it.opencodejrre-ran the remote-added reproduction from source and confirmed both sessions are listed with the change and only the post-remote one without.What it does not cover, so this is not a complete fix of the underlying cause:
session/stats.ts, for one — still see the narrow view.projectrows #59371 (/api/locationreturning the service's start directory rather than the caller's cwd) is a different layer and is deliberately not in this change.I verified the branch still applies cleanly to today's
dev(7ab721a97f38), and thatproject.tsandsession/projector.tsare untouched since this was reproduced, so the defect is still live.gaojunran commentedon Oct 31, 2026
A second consequence of the same ID switch, filed separately since it is an API-level failure and also hits sessions that stay visible: #60857 —
/session/:id/diffreturns HTTP 500Snapshot.Error: fatal: bad object <tree>for every turn captured before the ID change, because the snapshot store is keyed byproject.id(snapshot.ts:96) and never follows the new ID. Verified with a two-step repro on 2.1.2 (diff works,git remote add origin, same call 500s). Snapshots from v1-era sessions that were re-projected onto the new project ID are affected too, even though those sessions still appear in the list.