fix(opencode): persist and reuse session cursor#3735
Conversation
|
Important Review skippedAuto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Repository UI Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| permission: buildOpenCodePermissionRules(input.runtimeMode), | ||
| }), | ||
| ); | ||
| const resumeCursor = parseOpenCodeResumeCursor(input.resumeCursor); |
There was a problem hiding this comment.
🟡 Medium Layers/OpenCodeAdapter.ts:1103
When a session is stopped (via stopSession or the layer finalizer), stopOpenCodeContext calls session.abort, which destroys the upstream OpenCode session. But the persisted resumeCursor pointing to that now-dead session id is never cleared. The next startSession with that resumeCursor calls session.get on the aborted upstream session, which fails with a ProviderAdapterProcessError instead of restoring the conversation. Consider clearing or invalidating the persisted resumeCursor when stopOpenCodeContext destroys the upstream session, so the resume path does not try to recover from a session that no longer exists.
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/provider/Layers/OpenCodeAdapter.ts around line 1103:
When a session is stopped (via `stopSession` or the layer finalizer), `stopOpenCodeContext` calls `session.abort`, which destroys the upstream OpenCode session. But the persisted `resumeCursor` pointing to that now-dead session id is never cleared. The next `startSession` with that `resumeCursor` calls `session.get` on the aborted upstream session, which fails with a `ProviderAdapterProcessError` instead of restoring the conversation. Consider clearing or invalidating the persisted `resumeCursor` when `stopOpenCodeContext` destroys the upstream session, so the resume path does not try to recover from a session that no longer exists.
ApprovabilityVerdict: Needs human review 1 blocking correctness issue found. This PR introduces significant new session resume functionality with ~426 additions of new logic. Two unresolved review comments identify potential bugs: resume cursor not being cleared on abort, and premature turn completion during idle events. Human review recommended for both feature complexity and outstanding correctness concerns. You can customize Macroscope's approvability policy. Learn more. |
|
Addressed in the latest commit. Explicit stop/stopAll cleanup still aborts remote sessions; this change is limited to the orchestrated same-session restart path flagged by Bugbot. |
|
Addressed the latest Macroscope feedback in the latest commit. Same-session re-adoption now preserves the prior OpenCode context volatile in-memory state, including pending permissions/questions, part/text tracking, completed assistant parts, turn snapshots, and active turn metadata. That keeps outstanding permission replies and streamed message bookkeeping available after local context replacement while still limiting remote-session preservation to the matching resumeCursor path. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 2df4ec5. Configure here.
| messageRoleById: preservedSessionMemory?.messageRoleById ?? new Map(), | ||
| completedAssistantPartIds: preservedSessionMemory?.completedAssistantPartIds ?? new Set(), | ||
| turns: preservedSessionMemory?.turns ?? [], | ||
| activeTurnId: preservedSessionMemory?.activeTurnId, |
There was a problem hiding this comment.
Preserved turn id closes on idle
Medium Severity
When startSession restarts with a matching resume cursor, it copies the prior activeTurnId into the new context. After the new event subscription starts, a session.status idle event still runs the existing idle branch and emits turn.completed whenever that preserved id is set. OpenCode often reports idle while a permission is pending, so a preserve restart during an in-flight turn can mark the turn complete even though the approval is still open.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 2df4ec5. Configure here.
|
Noting that this overlaps with #3617. The intent of this PR is the narrower fix for #3604: persist/reuse the OpenCode session id and avoid silently replacing a valid persisted cursor with a fresh empty session if resume fails. I intentionally left cwd forking, stale-session fallback, and permission reapplication out of scope to keep the behavior change smaller. Happy to close this if maintainers prefer #3617’s broader approach or a different approach altogether. Otherwise I can keep this PR focused as the minimal alternative. |


What Changed
OpenCode sessions now persist a small resume cursor containing the upstream OpenCode session id.
When T3 Code recovers a provider session, the OpenCode adapter uses
session.getwith the persisted id instead of always creating a fresh upstream OpenCode session.The race cleanup path now only aborts OpenCode sessions created by the losing local start attempt, so an adopted durable session is not aborted.
Why
Fixes #3604.
Previously, after provider reaping or a server restart, follow-up messages could lose OpenCode conversation context because T3 Code created a new upstream OpenCode session.
This keeps the change adapter-local and avoids broader lifecycle, cwd-forking, permission reapplication, or reaper changes.
Manual Smoke Test
Verified locally with OpenCode:
t3code-opencode-resume-smoke-3604.Validation
corepack pnpm exec vp checkcorepack pnpm exec vp run typecheckcorepack pnpm exec vp run --filter t3 test -- apps/server/src/provider/Layers/OpenCodeAdapter.test.tsUI Changes
None.
Note
Medium Risk
Touches provider session lifecycle (resume vs create, conditional remote abort, and in-memory state preservation) where mistakes could drop conversation context or leave stray upstream sessions; behavior is well covered by new adapter tests.
Overview
OpenCode
startSessionnow exposes aresumeCursor(schemaVersion: 1, upstreamsessionId) on the returned session and honors an incoming cursor by callingsession.getinstead of alwayssession.create. Invalid or malformed cursors are ignored and behave like a fresh start.Restarting a thread with the same upstream session id no longer **
session.abort**s that remote session on teardown; in-memory adapter state (pending permissions/questions, turns, active turn, etc.) is carried over when the cursor matches the existing binding. ConcurrentstartSessionraces only abort newly created remote sessions, not adopted resumed ones; failedsession.getfor a valid cursor surfaces as a process error instead of silently creating an empty session.Tests extend the OpenCode runtime double with
session.get, permission replies, and delays, and add coverage for cursor return, resume, restart preservation, follow-up prompts, failure paths, and race cleanup.Reviewed by Cursor Bugbot for commit 2df4ec5. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Persist and reuse session cursor in
OpenCodeAdapter.startSessionstartSessionnow returns aresumeCursor({schemaVersion: 1, sessionId}) for new sessions and accepts one on subsequent calls to resume the same upstream session viaclient.session.getinstead of creating a new one.closeOpenCodeContexteffect supports closing local resources withabortRemote: false, used when handing off a session to a resumed context.stopOpenCodeContextnow delegates tocloseOpenCodeContextwithabortRemote: true, and malformed or version-mismatched cursors are treated as absent rather than erroring.Macroscope summarized 2df4ec5.