Skip to content

server: V1 migration fails every startup with SQLiteError: no such column: path #54009

Open

Description

@BambinoSK

Summary

The V1→V2 data migration fails on every server startup with SQLiteError: no such column: path while in the sessions phase. The migration marker never advances, so the import retries (and fails) on every subsequent start, surfacing a persistent "migration failed" notification in the TUI.

Environment

  • opencode version: 2.0.25
  • OS: Windows_NT 10.0.26300 (win32 x64)
  • Terminal: Windows console (opencode opened from command line); no $TERM/$TERM_PROGRAM set
  • Shell: cmd.exe / PowerShell
  • Install/channel: latest
  • Active plugins: none found in config

Reproduction

  1. Upgrade from V1 to V2 (config was in V1 format; config migration succeeded and produced opencode.json.v1.bak).
  2. Start the V2 server. Normal schema migrations complete; the background V1 data migration then fails during the sessions phase.
  3. Restart opencode. The migration resumes at sessions, fails the same way, and the TUI shows a "migration failed" notification on every startup.

Observed in ~/.local/share/opencode/log/opencode.log (repeats for every server run since 2026-10-06):

level=ERROR message="V1 migration failed" cause="Cause([Die(SQLiteError: no such column: path)])" role=server

Expected Behavior

  • The sessions phase of the V1 import completes (or skips cleanly when there is nothing left to import).
  • The migration.v1-v2 marker advances/ completes so the import is not retried on every startup.
  • Missing columns on legacy tables should be projected as NULL or handled with a named compatibility error, not an unhandled SQLite error.

Actual Behavior

  • The migration is durably stuck at {"phase":"sessions"}:

    SELECT value FROM kv WHERE key = 'migration.v1-v2';
    -- {"phase":"sessions"}
  • The legacy session table in opencode.db has no path column (it has directory; only session_v2 has path), yet the sessions-phase importer references path:

    .schema session
    CREATE TABLE session (
        id TEXT PRIMARY KEY,
        project_id TEXT NOT NULL,
        parent_id TEXT,
        slug TEXT NOT NULL,
        directory TEXT NOT NULL,
        title TEXT NOT NULL,
        version TEXT NOT NULL,
        share_url TEXT,
        ...
        workspace_id TEXT,
        ...
    );  -- no `path` column

    Note: schema migration 20260428004200_add_session_path is recorded as completed in the migration table, but the legacy session table still lacks path.

  • Row counts in the affected database: session=1, session_v2=24, message=1, part=1, session_message=108, project=8 — i.e. most sessions already imported; one legacy row appears to be the one that cannot complete.

  • Every startup: schema migrations complete, then V1 migration failed ... no such column: path, TUI notification shown.

Additional Context

Activity

  1. opencode-agent commented on Oct 8, 2026

    @opencode-agent
    Contributor

    Thanks for the detailed report. I reproduced this on 2.0.25. The setup was a fresh V2 database plus a V1-format legacy session table without a path column and the migration.v1-v2 marker set to {"phase":"sessions"}. Every server start then logs V1 migration failed ... SQLiteError: no such column: path, and the marker never advances.

    The sessions-phase importer reads path directly from the legacy session table, so a legacy table without that column makes the import fail. It still happens on the latest v2 branch (8059cdcb), run from source. The script below reproduces it with an isolated data directory.

    Reproduction script (repro.sh)
    #!/usr/bin/env bash
    # Reproduces anomalyco/opencode#54009: the V1->V2 import dies with
    # "SQLiteError: no such column: path" when the legacy `session` table has no `path` column.
    # Usage: ./repro.sh                 (runs `opencode` from PATH, e.g. @opencode/cli@2.0.25)
    #        OC_DEV=/path/to/checkout ./repro.sh   (runs the CLI from a v2 source checkout)
    set -euo pipefail
    WORK=$(mktemp -d)
    export HOME=$WORK/home XDG_DATA_HOME=$WORK/home/.local/share XDG_CONFIG_HOME=$WORK/home/.config \
      XDG_STATE_HOME=$WORK/home/.local/state XDG_CACHE_HOME=$WORK/home/.cache
    mkdir -p "$WORK/proj" "$HOME"
    cd "$WORK/proj"
    oc() {
      if [ -n "${OC_DEV:-}" ]; then
        bun --conditions=browser --preload="$OC_DEV/packages/tui/node_modules/@opentui/solid/scripts/preload.js" \
          "$OC_DEV/packages/cli/src/index.ts" "$@"
      else
        opencode "$@"
      fi
    }
    start_server() {
      setsid bash -c "$(declare -f oc); oc serve --port 4911" >/dev/null 2>&1 &
      local pid=$!
      sleep 15
      kill -- -"$pid" 2>/dev/null || true
      sleep 2
    }
    DATA=$XDG_DATA_HOME/opencode
    DB=$DATA/opencode.db; [ -n "${OC_DEV:-}" ] && DB=$DATA/opencode-local.db  # source checkouts use the "local" channel DB
    
    echo "== 1st start: bootstrap a fresh V2 database"
    start_server
    
    echo "== add a V1-format legacy session table WITHOUT a path column (as in the report)"
    cat > "$WORK/seed.ts" <<'EOF'
    import { Database } from "bun:sqlite"
    const db = new Database(process.argv[2])
    db.run(`CREATE TABLE session (
      id TEXT PRIMARY KEY, project_id TEXT NOT NULL, parent_id TEXT, slug TEXT NOT NULL,
      directory TEXT NOT NULL, title TEXT NOT NULL, version TEXT NOT NULL, share_url TEXT,
      summary_additions INTEGER, summary_deletions INTEGER, summary_files INTEGER, summary_diffs TEXT,
      metadata TEXT, cost REAL DEFAULT 0 NOT NULL, tokens_input INTEGER DEFAULT 0 NOT NULL,
      tokens_output INTEGER DEFAULT 0 NOT NULL, tokens_reasoning INTEGER DEFAULT 0 NOT NULL,
      tokens_cache_read INTEGER DEFAULT 0 NOT NULL, tokens_cache_write INTEGER DEFAULT 0 NOT NULL,
      revert TEXT, permission TEXT, agent TEXT, model TEXT, workspace_id TEXT,
      time_created INTEGER NOT NULL, time_updated INTEGER NOT NULL, time_compacting INTEGER, time_archived INTEGER)`)
    db.run(`CREATE TABLE IF NOT EXISTS message (id TEXT PRIMARY KEY, session_id TEXT NOT NULL, time_created INTEGER NOT NULL, time_updated INTEGER NOT NULL, data TEXT NOT NULL)`)
    db.run(`CREATE TABLE IF NOT EXISTS part (id TEXT PRIMARY KEY, message_id TEXT NOT NULL, session_id TEXT NOT NULL, time_created INTEGER NOT NULL, time_updated INTEGER NOT NULL, data TEXT NOT NULL)`)
    const now = Date.now()
    db.run(`INSERT INTO session (id, project_id, slug, directory, title, version, time_created, time_updated)
      VALUES ('ses_legacy0001', 'global', 'legacy', '/tmp', 'Legacy session', '1.18.0', ?, ?)`, [now, now])
    db.run(`INSERT OR REPLACE INTO kv (key, value, time_created, time_updated) VALUES ('migration.v1-v2', '{"phase":"sessions"}', ?, ?)`, [now, now])
    EOF
    bun "$WORK/seed.ts" "$DB"
    
    for i in 2 3; do
      echo "== start #$i"
      start_server
    done
    
    echo "== V1 migration log lines:"
    grep -h "V1 migration" "$DATA"/log/*.log || echo "(none)"
    echo "== migration marker:"
    bun -e "const {Database}=require('bun:sqlite');console.log(new Database('$DB').query(\"select value from kv where key='migration.v1-v2'\").get())"

    Run it: bash repro.sh

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions