Skip to content

project: adding a git remote changes the project ID and hides every earlier session #57985

Open

Description

@platypusrex

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), local agentcraft.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        # empty

Expected 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 second project row 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 with opencode -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 for permission. Sessions reappeared in /sessions straight away, without restarting the service.

Activity

  1. opencode-agent commented on Oct 22, 2026

    @opencode-agent
    Contributor

    Thanks for the clear report and repro. I could reproduce this on 2.0.32, and it still happens on the latest dev (248bfbe11):

    1. git init and an empty root commit in a fresh directory.
    2. opencode api session.create --standalone ...: the session gets a project ID based on the root commit, and opencode session list --standalone lists it.
    3. git remote add origin [email protected]:example/repro.git.
    4. opencode session list --standalone now 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 global project and in a project ID read from a legacy .git/opencode file, 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)
    #!/usr/bin/env bash
    # Reproduces #57985: adding a git remote changes the project ID and hides earlier sessions.
    # Usage: ./repro.sh            (uses `opencode` from PATH)
    #        From source (the --standalone child is re-spawned via bun, so pass flags via BUN_OPTIONS):
    #        BUN_OPTIONS="--conditions=browser --preload=<repo>/packages/tui/node_modules/@opentui/solid/scripts/preload.js" \
    #          OC="bun <repo>/packages/cli/src/index.ts" ./repro.sh
    set -u
    OC=${OC:-opencode}
    export HOME=$(mktemp -d)            # isolated data dir / database
    export XDG_DATA_HOME=$HOME/.local/share XDG_CONFIG_HOME=$HOME/.config XDG_STATE_HOME=$HOME/.local/state XDG_CACHE_HOME=$HOME/.cache
    DIR=$(mktemp -d)/repro
    mkdir -p "$DIR" && cd "$DIR"
    git init -q && git -c user.email=a@b -c user.name=a commit -q --allow-empty -m root
    echo "== version: $($OC --version)"
    echo "== create session before remote"
    $OC api session.create --standalone --data '{"title":"created before the remote"}'
    echo; echo "== session list (before remote)"
    $OC session list --standalone
    git remote add origin [email protected]:example/repro.git
    echo "== session list (after git remote add)"
    $OC session list --standalone
    echo "== (end; empty list above = bug)"
    DB=$(find "$HOME" -name '*.db' 2>/dev/null | head -1)
    if [ -n "$DB" ] && command -v sqlite3 >/dev/null; then
      echo "== projects:"; sqlite3 "$DB" "select id, worktree, vcs from project"
      echo "== sessions:"; sqlite3 "$DB" "select id, project_id, title from session_v2"
    fi

    Run it: bash repro.sh

  2. loofahss commented on Oct 23, 2026

    @loofahss

    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 global project are moved".

    Isolated repro (own XDG_DATA_HOME/XDG_CONFIG_HOME, no plugins):

    mkdir -p work/parent/sub
    (cd work/parent/sub && opencode api session.create --standalone --data '{"title":"sub-before-git"}')
    (cd work/parent     && opencode api session.create --standalone --data '{"title":"parent-before-git"}')
    cd work/parent && git init -q && git commit -q --allow-empty -m root
    opencode session list --standalone        # from parent
    (cd sub && opencode session list --standalone)
    

    Before git init the two sessions get path-based project ids (007f4a82… for parent,
    5c825dbb… for parent/sub), each listed in its own directory. After git init + root commit
    (cd4c6bb8…), both sessions have session_v2.project_id = cd4c6bb8… and both are listed from
    either directory. The two old path-based project rows are still present (not deleted), so this
    does not look like the migrateProjectId old→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_directory stays empty
    in 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.

  3. holny commented on Oct 24, 2026

    @holny
    Contributor

    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.

  4. argszero commented on Oct 29, 2026

    @argszero
    Contributor

    There is a pull request open for this defect: #59946, which closes #59371 — the same problem reported from the session list side.

    What it does: packages/core/src/session/store.ts lists sessions for every project row 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 project rows 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 the global row, which must never be matched: its worktree is whichever commitless repository resolved to it first, so matching it cross-listed unrelated directories — that case fails without the exclusion and passes with it. opencodejr re-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:

    I verified the branch still applies cleanly to today's dev (7ab721a97f38), and that project.ts and session/projector.ts are untouched since this was reproduced, so the defect is still live.

  5. gaojunran commented on Oct 31, 2026

    @gaojunran

    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/diff returns HTTP 500 Snapshot.Error: fatal: bad object <tree> for every turn captured before the ID change, because the snapshot store is keyed by project.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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions