Skip to content

fix(core): do not reuse an unmigrated V1 session ID on create - #62014

Open
0xABCD01 wants to merge 2 commits into
anomalyco:devfrom
0xABCD01:session-id-shadow
Open

0xABCD01 wants to merge 2 commits into
anomalyco:devfrom
0xABCD01:session-id-shadow

Conversation

@0xABCD01

@0xABCD01 0xABCD01 commented Nov 5, 2026 •

Copy link
Copy Markdown

Issue for this PR

Closes #61947

Type of change

  • Bug fix
  • New feature
  • Refactor / code improvement
  • Documentation

What does this PR do?

After the V1 to V2 update, some V1 sessions were never migrated (#50130).
Continuing one of those by ID missed the V2 lookup, and session creation
then reused the requested ID for a new empty session in the current
project. From then on the ID resolved to that empty session and the
original, history and all, was unreachable.

Session.create now checks the legacy V1 session table when it is
asked to create with an explicit ID that has no V2 row. If a V1 session
still holds that ID, create fails with SessionNotFoundError instead of
inserting the shadow row. Nothing is imported here; importing
unmigrated sessions is #54272's scope. Databases without a legacy
session table skip the check.

How did you verify your code works?

In packages/core (bun run test):

  • New tests in test/session-create.test.ts: creating with an ID held
    by a V1-only row fails with NotFoundError and inserts no V2 row (this
    test fails on unpatched code); creating with a fresh explicit ID and
    with no ID still works when a V1 table is present.
  • session-create, session-list, session-store: 55 pass. Migration
    suites: 59 pass. bun typecheck clean.

End to end with a seeded database (V1 row present, migration marked
completed): POST /api/session with that ID returned 200 and created
the shadow session before this change; with it, the same request
returns 404 SessionNotFoundError, no session_v2 row is written, and
the V1 row is untouched. Fresh-ID and no-ID creates still return 200.

Checklist

  • I have tested my changes locally
  • I have not included unrelated changes in this PR

Added by the opencode bot (AI-generated).

Related: #50130

Public API: plugin session.create can fail with new Session.UnmigratedError; POST /api/session returns 404 with a distinct message for unmigrated V1 IDs (backwards compatible)

@opencodejr opencodejr Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fix works. I ran the #61947 reproduction script from source on the base and on this branch. On the base, run -s <id> creates a session_v2 row that reuses the ID of the session that exists only in V1, in the other project's directory, and session export returns that empty session. With this change, run -s <id> fails, no V2 row is written and the V1 row is untouched. The new test fails on the base and passes here. session-create, v1-migration, session-list and session-store tests pass. The package's typecheck script fails before checking anything (Script not found "check"), so I ran its typecheck:tsc script instead, which is clean.

Public API: no fields or routes change. POST /api/session with an explicit ID still held by a V1 session now returns 404 SessionNotFoundError instead of 200. This is backwards compatible for callers that already handle a 404 from create.

The check is in the right layer (server-side create, so every client is covered). My one comment is about what users see when this happens (inline). It's worth fixing but doesn't block a merge.

Related: #50130

Comment thread packages/core/src/session.ts Outdated
.pipe(Effect.orDie)
if (legacyTable) {
const legacy = yield* db.get(sql`SELECT id FROM session WHERE id = ${sessionID}`).pipe(Effect.orDie)
if (legacy) return yield* new NotFoundError({ sessionID })

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Returning a plain NotFoundError here means a user running opencode -s <id> only sees Session not found: ses_…, which is the same message as for an ID that doesn't exist. Their session does exist, but only in V1 data that was never migrated, so the message points them the wrong way. Could this case get its own error, or at least a message such as "session exists in V1 data but was not migrated", so the CLI and other clients can tell users what actually happened?

@opencodejr opencodejr Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The earlier point about the error message is addressed. run -s <id> on an ID held only by an unmigrated V1 session now fails with Session exists in V1 data but was not migrated: ses_…, and still writes no V2 row (re-checked with the #61947 reproduction script from source). session-create and handler-policy tests pass. The packages' typecheck scripts don't run in this checkout (Script not found "check"), so I ran their typecheck:tsc scripts for core and server instead; both are clean.

Public API: POST /api/session keeps the 404 SessionNotFoundError shape; only the message differs for this case. The plugin API's session.create can now also fail with the new Session.UnmigratedError, but only for explicit IDs still held by a V1 session, which previously created a duplicate session without any error. Backwards compatible otherwise.

Nothing new to raise.

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

session: -s <id> from another project 404s and silently creates a shadow session with the same ID

1 participant