Repository navigation
fix(core): keep plan mode reminders in sync with session agent - #42652
Merged
Merged
Conversation
rekram1-node
force-pushed
the
fix-plan-mode
branch
from
August 16, 2026 20:46
0dbbfa9 to
c0fdcae
Compare
jinhuang712
pushed a commit
to jinhuang712/opencode
that referenced
this pull request
Sep 26, 2026
4 of 6 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The plan plugin communicates Plan mode to the model via
<system-reminder>synthetic messages injected onsession.created/session.agent.selected. Those messages live in the transcript, but the mode itself lives in durable session state (SessionTable.agent) — and several operations edit the transcript without touching the agent:SessionHistory.messageRowsloadsseq >= compaction.seq), dropping the enter reminder while the session stays onplan.SessionTable.agentstays put and nosession.agent.selectedre-fires. Undoing past the switch into plan leaves the model unaware it's in Plan mode (including the undo-to-empty-session case); undoing past the switch out of plan leaves a stale enter reminder so the model wrongly refuses to edit.In all cases the tool hook still hard-blocks edits, but the model has a wrong or missing idea of what mode it is in.
Fix
Reconcile the transcript against the durable agent on every model request via
ctx.session.hook("context"):planwith no live enter reminder, or offplanwith a stale one, insert the correct reminder into the current request — before the trailing user message, matching where agent-switch reminders land — and persist a durable copy viactx.session.syntheticso subsequent requests find it and the reconciler goes back to a no-op.The steady state is unchanged: durable enter/leave synthetics on agent switches do the work, and the reconciler does nothing while transcript and agent agree. Reminders only ever land near the request tail and earlier messages are never rewritten, so the prompt cache prefix stays warm; healing costs one small uncached insertion, once.
This matches how Claude Code and Grok handle mode reminders (persisted, append-only, re-derived from mode state at prompt time); their periodic full/sparse re-assertion cadence is intentionally not adopted.
Tests
test/plugin/plan.test.tsruns the real plugin effect against a stubbed plugin context:Reminder texts in tests are derived by invoking the plugin rather than duplicating the strings.