5.6 KiB
Session Continuity Fix Summary
Problem
The app was creating new OpenCode sessions for each message instead of reusing existing session, breaking conversation continuity. This affected:
- Continue when error - When a message failed and was retried with a fallback model
- Messages in the same chat - Each subsequent message created a new OpenCode session instead of continuing in the existing one
- Builder proceed with build - Build messages created new sessions
- Redo operations - Redoing a message didn't preserve the original session
Root Causes
-
Client-side missing opencodeSessionId: The frontend (
builder.js,app.js) wasn't sendingopencodeSessionIdin the message payload when creating new messages. -
Server-side overwriting session IDs: The server's
ensureOpencodeSession()function was creating new OpenCode session IDs even when one already existed, if validation failed or for certain edge cases. -
Session detection overwriting: When a new OpenCode session was auto-created by the CLI, code was overwriting the existing
initialOpencodeSessionId.
Fixes Applied
1. Client-side (builder.js)
- sendMessage(): Added logic to preserve
session.opencodeSessionIdin message payload when sending messages - executeBuild(): Added logic to preserve
session.opencodeSessionIdwhen starting a build process - redoProceedWithBuild(): Added logic to preserve
session.opencodeSessionIdwhen redoing a build - triggerFallback(): Enhanced to prioritize message's
opencodeSessionIdover session's (in case message has more recent session)
2. Client-side (app.js)
- sendMessage(): Added logic to preserve
session.opencodeSessionIdin message payload for regular chat UI
3. Server-side (server.js)
-
ensureOpencodeSession():
- When session validation fails, now returns the existing
session.opencodeSessionIdinstead of creating a new one - This prevents creating new sessions when CLI operations timeout or fail temporarily
- When session validation fails, now returns the existing
-
createOpencodeSession() section:
- Only sets
initialOpencodeSessionIdif not already set - This prevents overwriting a locked initial session with a new auto-generated one
- Only sets
-
handleNewMessage():
- Explicitly logs when using
opencodeSessionIdfrom request body vs inheriting from session - Ensures both paths are properly tracked
- Explicitly logs when using
-
processMessage():
- Uses
message.opencodeSessionIdif explicitly provided in the request - Falls back to
ensureOpencodeSession()only if no explicit session ID - This ensures retried/continued messages use the same session
- Uses
-
sendToOpencode():
- Only updates session ID from captured events if no explicit session was passed
- Prevents overwriting a passed
--sessionarg with an auto-generated one
-
Session recovery section:
- Only sets
opencodeSessionIdto the detected ID if no session ID already exists - Preserves
initialOpencodeSessionIdand uses it as the final session ID - Added logging for both scenarios (new initial session detected vs preserving existing)
- Only sets
How It Works Now
First Message in a Session
- Client sends message without
opencodeSessionId - Server's
processMessage()callsensureOpencodeSession() ensureOpencodeSession()creates a new OpenCode session (or lets CLI create one)- CLI reports session ID in JSON events
- Server captures and stores it as
session.opencodeSessionIdandsession.initialOpencodeSessionId - Session is now locked for all future messages
Subsequent Messages
- Client sends message with
opencodeSessionIdfromsession.opencodeSessionId - Server's
processMessage()uses the explicitmessage.opencodeSessionId sendToOpencode()is called with the explicit session ID- CLI runs with
--session <session-id>argument - Same OpenCode session is used, maintaining conversation continuity
Continue When Error / Retry
- Original message fails, triggering
triggerModelFallback() - Fallback creates a new message with
isContinuation: true - New message includes the original message's
opencodeSessionIdin payload - Server uses the explicit session ID, continuing in the same OpenCode session
- Conversation context is preserved across the error
Redo Operations
- Proceed-with-build redo: Uses
redoProceedWithBuild()which preservessession.opencodeSessionId - Regular opencode redo: Uses
/api/sessions/:id/messages/:id/redoendpoint which usessession.opencodeSessionId - Both maintain the same OpenCode session
Key Changes
Session ID Preservation
- Never overwrite
initialOpencodeSessionIdonce it's set - Prefer explicit session IDs from messages over auto-detected ones
- Preserve across all operations: send, redo, retry, continue
Validation Failure Handling
- Don't create new session when validation fails
- Return existing session and let CLI handle it
- Log extensively for debugging session continuity issues
Testing Checklist
- Send first message in a new session - should create new OpenCode session
- Send second message in same session - should reuse existing session
- Message fails and auto-retries - should continue in same session
- Click "Redo" on a message - should use same session
- Click "Proceed with Build" - should use same session
- Click "Redo Build" - should use same session
- Refresh page and send new message - should use existing session
Files Modified
chat/public/builder.js- Client-side builder UIchat/public/app.js- Client-side regular chat UIchat/server.js- Server-side session management