Skip to content

project: session list returns empty in a directory that became a git repo after first open #61009

Open
xzyangha/opencode
#1

Description

@csansone

Description

opencode session list returns nothing for a directory that opencode first opened before it became a git repo, even though sessions exist.

The project registry ends up with two entries for the same directory:

  • 9ed4d1ef… — registered pre-git (no vcs), and still the identity that CLI invocations resolve and mark active
  • efe4468c… — registered on the next session after git init + first commit (vcs: git, id equals git rev-parse HEAD)

All sessions are stored under the newer VCS identity, but session list resolves the stale pre-git entry and finds zero sessions. Resuming by explicit ID (opencode -s <id>) and GET /api/session?directory=<dir> both work, which points at project resolution rather than storage.

A session started after git init also reported Is directory a git repo: no in its harness environment snapshot, so the stale identity appears to win in more places than just session list.

Expected: the CLI should resolve the same project identity the sessions are stored under — prefer the VCS entry once the directory has a repo, or deduplicate identities that share a canonical directory.

Workarounds: opencode api get "/api/session?directory=$(pwd)", or resume explicitly with opencode -s <sessionID>.

Plugins

None — user config only contains {"$schema": "https://opencode.ai/config.json"}.

OpenCode version

v2.2.2 (channel: latest)

Steps to reproduce

  1. Open opencode in a plain, non-git directory and create a session. This registers project A (id = directory hash, no VCS).
  2. git init && git commit in that directory.
  3. Open opencode again and create a session. This registers project B (vcs: git, id = HEAD commit hash); sessions are now stored under B.
  4. Run opencode session list in the directory → empty (--format json → []).
  5. opencode api get "/api/session?directory=$(pwd)" → the sessions exist, all with projectID = B.

Consistently reproducible. Control: in a directory that was a git repo before first being opened, session list shows all 44 sessions.

Observed data (Linux; timestamps are epoch ms):

$ opencode api get /api/project | jq '.[] | select(.canonical=="/home/<user>/projects/<project>")'
{"id":"9ed4d1ef70cc462c80c0de397404b84c8e28323f","canonical":"/home/<user>/projects/<project>","time":{"created":1793331031829,"updated":1793331031829,"active":1793506432664},"sandboxes":[]}   # created 2026-10-30 (pre-git), still receiving active bumps from CLI runs
{"id":"efe4468cff211797ecc2935bafb7db9931aad692","canonical":"/home/<user>/projects/<project>","vcs":"git","time":{"created":1793504166172,"updated":1793504166172,"active":1793504166172},"sandboxes":[]}   # created 2026-10-31 23:42, after git init at ~23:33

$ opencode api get "/api/session?directory=/home/<user>/projects/<project>" | jq -r '.data[].projectID'
efe4468cff211797ecc2935bafb7db9931aad692   # all 4 sessions

$ opencode session list --format json
[]

$ git rev-parse HEAD
efe4468cff211797ecc2935bafb7db9931aad692

Screenshot

n/a — API outputs above.

Operating System

Linux x86_64 (Ubuntu, kernel 5.15.0-179-generic)

Terminal

tmux (TERM=xterm-256color, COLORTERM=truecolor)

Related: #52501 — same failure class (split project identity → empty session list), there via Windows path case.

Activity

  1. opencodejr commented on Nov 1, 2026

    @opencodejr
    Contributor

    Thanks for the detailed report. I reproduced this on 2.2.2, and it still happens on the latest dev (01276f9).

    Steps, with isolated data and config directories and no plugins:

    1. Create a session in a plain directory. It gets a project id that is a hash of the directory, and opencode session list shows it.
    2. Run git init and make an empty commit.
    3. Create another session. It gets the commit hash as its project id, and the first session is moved to that id too.
    4. opencode session list --format json returns [].

    GET /api/location for the directory still returns the old pre-git project id, and that is the id session list filters on. /api/project shows two rows for the directory, and GET /api/session?directory=<dir> returns both sessions under the git id. So your sessions are stored correctly. The problem is that the server's loaded location for the directory keeps the project it resolved before git init. That probably also explains why new sessions saw Is directory a git repo: no.

    The script below reproduces it. Related: #59371 and its fix PR #59946 (duplicate project rows for one directory hide sessions), #57985 (a project split caused by adding a git remote) and #52501 (the same kind of split caused by Windows path case).

    Reproduction script (repro.sh)
    #!/usr/bin/env bash
    # Repro for #61009: session list empty after a directory becomes a git repo.
    # Usage: bash repro.sh            (uses `opencode` on PATH)
    #        OC="bun ... /path/to/packages/cli/src/index.ts" bash repro.sh
    # MODE=standalone uses --standalone; default uses the shared background service.
    set -u
    OC=${OC:-opencode}
    ROOT=$(mktemp -d /tmp/opencode/r61009.XXXX)
    export XDG_DATA_HOME=$ROOT/data XDG_CONFIG_HOME=$ROOT/config XDG_STATE_HOME=$ROOT/state XDG_CACHE_HOME=$ROOT/cache
    mkdir -p "$XDG_CONFIG_HOME/opencode" && echo '{}' > "$XDG_CONFIG_HOME/opencode/opencode.json"
    FLAG=""; [ "${MODE:-service}" = standalone ] && FLAG="--standalone"
    # Optional: SERVE="<cmd to start server>" starts a private server (needed when running from source).
    if [ -n "${SERVE:-}" ]; then export OPENCODE_PASSWORD=repro61009; (cd "${SERVE_CWD:-.}" && $SERVE serve --port 45991 >"$ROOT/serve.log" 2>&1 &) ; for i in $(seq 60); do curl -s localhost:45991 >/dev/null && break; sleep 1; done; FLAG="--server http://127.0.0.1:45991"; fi
    D=$ROOT/proj; mkdir -p "$D"; cd "$D"
    export GIT_AUTHOR_NAME=a GIT_AUTHOR_EMAIL=a@a GIT_COMMITTER_NAME=a GIT_COMMITTER_EMAIL=a@a
    
    echo "== 1. session in plain (non-git) dir"
    $OC api post /api/session $FLAG --data "{\"title\":\"pre-git\",\"location\":{\"directory\":\"$D\"}}" | head -c 300; echo
    $OC session list $FLAG --format json | head -c 300; echo
    
    echo "== 2. git init + commit"
    git init -q && git commit -q --allow-empty -m root && git rev-parse HEAD
    
    echo "== 3. new session after git"
    $OC api post /api/session $FLAG --data "{\"title\":\"post-git\",\"location\":{\"directory\":\"$D\"}}" | head -c 300; echo
    
    echo "== 4. session list"
    $OC api get "/api/location?location[directory]=$D" $FLAG; echo
    $OC session list $FLAG --format json; echo
    echo "== 5. projects"
    $OC api get /api/project $FLAG; echo
    echo "== 6. sessions by directory"
    $OC api get "/api/session?directory=$D" $FLAG | head -c 1500; echo
    echo "== location"
    $OC api get "/api/location?location[directory]=$D" $FLAG; echo
    [ -z "$FLAG" ] && $OC service stop >/dev/null 2>&1
    [ -n "${SERVE:-}" ] && pkill -f "serve --port 45991"
    true

    Run it: bash repro.sh

  2. xzyangha commented on Nov 2, 2026

    @xzyangha

    Taking this one: the location graph is cached for the life of the server and resolves its project once, so a directory opened before git init keeps the markerless project identity — sessions are then stored under the VCS identity while session list resolves the stale one. Fix + regression test in progress (rebuild the graph when a VCS marker appears).

  3. maucabreradev commented on Nov 4, 2026

    @maucabreradev

    Follow-up on v2.2.5 (Linux): the stale pre-git project/location resolution described here also
    silently disables snapshot capture, which makes TUI undo/redo roll the conversation back
    while the files keep the assistant's changes. Not filing a new issue — same root cause; adding
    the snapshot-side evidence in case it helps validate the fix.

    Observed data — same duplicate-rows pattern as reported above (ids truncated; times relative
    to first project registration):

    {id:"b7136a3b…", canonical:"/home/<user>/dev/<dir>", created: T+0}
    {id:"global",    canonical:"/home/<user>/dev/<dir>", vcs:"git", created: T+16min}   # git init'ed, unborn HEAD
    {id:"0ba8e998…", canonical:"/home/<user>/dev/<dir>", vcs:"git", created: T+20min}   # == `git rev-parse HEAD` of the first commit
    

    (the global row matches #61249's premise: an unborn repo resolves to the shared global id)

    Snapshot / undo consequence

    • The directory was first opened before git init, and the running background service never
      re-resolved it. With location.vcs cached as non-VCS for the life of the process,
      Snapshot.enabled() stayed false and capture silently no-oped for every step:
      • the internal snapshot store (~/.local/share/opencode/snapshot/) was never created,
      • assistant messages of later steps carried no snapshot field at all (after recovery they do:
        snapshot: {start, end, files: ["index.htm"]}),
      • staged reverses were recorded with files: [], so nothing was attributed/restored on disk.
    • Net effect for the user: the chat rolls back normally, files remain edited. No warning or
      signal anywhere in the logs; "capture is best effort" makes this invisible until you try to
      undo a real edit. A one-line warning when file-touching steps run with location.vcs unset
      would help, but the cache fix below is the real solution.
    • opencode service restart immediately re-resolved the location: capture started, undo began
      restoring files (verified end-to-end: edit a tracked file → stage revert with files: true
      returns the file patch → commit → file content restored). This matches the approach in fix(core): rebuild a location after its directory becomes a git repo #61314:
      since file restore gates on location.vcs?.type === "git", rebuilding the location graph when
      a VCS marker appears should re-enable snapshots for new steps as well.

    Minimal verification I used (server API, after any workaround restart):

    1. opencode service restart
    2. Run a prompt that edits a tracked file in a fresh session → assistant message now has
      snapshot.files: [<relative path>]
    3. POST /api/session/{id}/revert/stage {"messageID":<user msg>, "files":true} → response
      includes the per-file patch; POST /api/session/{id}/revert/commit → file content reverts.

    Reproduced consistently on v2.2.5. Happy to test #61314 against this scenario if useful.

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