Conversation
|
Thanks for your contribution! This PR doesn't have a linked issue. All PRs must reference an existing issue. Please:
See CONTRIBUTING.md for details. |
|
The following comment was made by an LLM, it may be inaccurate: Potential Duplicate Found:
Why it's related: Both PRs address the same core issue of recovering from stale encrypted reasoning tokens that expire or become invalid when replayed. PR #43595 appears to be an earlier attempt at handling this problem, while the current PR #48908 implements a more comprehensive solution with reactive auto-recovery in the Session Runner, error classification, and extensive test coverage. |
|
Read through the sanitize→resend chain and it holds up on our side — One durability concern: Related: the post-replay assertion only matches Minor: the update loop rewrites every assistant message in the window, even untouched ones. Filtering to the messages |
…or projection durability
|
Thanks for updating your PR! It now meets our contributing guidelines. 👍 |
|
Good catch on the projection replay. Pushed an update:
|
|
Checked the update — all three points land correctly. Routing through |
|
@Mirrobot-Agent Check this PR, is this a good fix? |
|
On it — tracing the full recovery chain before I call it: the new classification in |
Mirrobot-Agent
left a comment
There was a problem hiding this comment.
Verdict: commented — the fix is the right shape and well-tested; one design sign-off plus minor notes stand between it and approval.
cc @marwanvx
Overall Assessment
This is a careful, correctly-placed fix. It mirrors the existing overflow/continuation recovery — classify in @opencode/ai, recover in the runner only while nothing was streamed, retry once with clean history — and the durability claim survives tracing:
- The new classification is gated to client-scoped failures (
packages/ai/src/provider-error.ts:130-143), so 5xx/transport noise cannot earn the label. bus.publishruns projectors inside the event transaction (packages/core/src/bus.ts:399-402), so by the timeRecoverStaleReasoningfinishes,SessionMessageTablealready holds the sanitized content; the retried attempt reloads history from the DB (llm.ts:233→SessionHistory.entriesForRunner), so the second request goes out clean by construction, not by timing luck.- The wire effect is real:
lowerReasoning(packages/ai/src/protocols/open-responses.ts:529) drops the reasoning part oncestateis gone, while visible text and every other part survive. - The replay test is not cosmetic —
replaySessionProjectiondeletesSessionMessageTableand re-foldsEventTable, and the addedstateassertion proves the sanitized form survives a rebuild, which is exactly the durability concern raised in the thread. - The event never reaches clients:
event-feed.ts:49forwards onlyisOpenCodeEvent, andsession.message.content.updatedis not inServerDefinitions.
I could not execute the suite in my sandbox (bun is not available in this session), so the checks above are from tracing the code paths and from the construction of the added tests rather than a local run.
🟠 Major
packages/core/src/session/runner/llm.ts:316— this revives live publication of an event the repo retired in #48043 and marked replay-only; needs an explicit maintainer ack before merge (details inline).
🟡 Minor
packages/core/src/session/runner/llm.ts:318-326— one durablepublishper modified message means N transactions/lock acquisitions;bus.publishAllwould commit the batch atomically.packages/core/test/session-runner.test.ts— no companion test for the one-shot guard: after a second stale-reasoning rejection the session should fail normally instead of stripping again or looping. The flag makes that true today; a test would keep it true.packages/ai/test/provider-error.test.ts— the new cases only exercise 400s with a classification present. A negative case (non-client status with the same text stays non-stale) and a directisStaleReasoningFailurecase for the regex fallback would pin the gate down.
🔵 Info
packages/ai/src/provider-error.ts:74-78— the fallback branch fires on unclassified errors whose text merely mentions the phrases; the strip is permanent, so it is worth a deliberate note that only affirmatively-classified failures proceed (or a comment saying why the fallback exists).- #43595 (the duplicate bot's find) is still open and overlaps; maintainers will want to close one of the two.
This review was generated by an AI assistant.
| RecoverFull: Effect.fnUntraced(function* () { | ||
| recoverContinuation = false | ||
| }), | ||
| RecoverStaleReasoning: Effect.fnUntraced(function* () { |
There was a problem hiding this comment.
🟠 Major — Design sign-off needed: this resumes live publication of session.message.content.updated, which #48043 deliberately retired ("remove message content mutation API"), and which is documented as replay-only (packages/schema/src/session-event.ts:135) and excluded from the public manifest. It is mechanically sound — the projector still folds it, the event never reaches SSE clients (event-feed.ts:49 filters on isOpenCodeEvent), and UsageRecorded sets a precedent for internal durable events the runner publishes live — but it re-opens the content-mutation path that removal closed, minus the guards the old public API enforced (message completed, no unfinished tools, session idle). Before merge, get an explicit ack from the maintainer who owns #48043 that this internal revival is the intended direction; if it lands, the // Replay-only comment should be updated so the file does not misdocument itself. This is not a code defect — it is a merge decision that belongs to the maintainers, flagged so it is made consciously.
|
Not to complain but why are we using agents to review this simple PR, this should've been pushed since yesterday from how simple the change is. |
…reasoning on provider rejection
There are reasons not to push this, and instead have the provider fix the problem. |
Issue for this PR
Closes #48741
Type of change
What does this PR do?
When resuming sessions or continuing tool execution with Responses API models (e.g. Muse Spark / OpenAI models on Zen/Console gateways), requests fail when replaying expired or caller-mismatched reasoning state:
reasoning 'encrypted_content' was not issued to this callerinvalid_encrypted_contentReferenced reasoning item '...' was not found or has expiredThis handles the rejection transparently in the session runner:
stale-reasoningin@opencode/ai.reasoningEncryptedContentanditemId) from assistant messages while preserving visible text.SessionEvent.MessageContentUpdatedon the bus so sanitized state is durable inEventTableand survives projection rebuilds (replaySessionProjection).Related: #48773, #48805
How did you verify your code works?
bun test test/provider-error.test.tsinpackages/ai(all passed)bun test test/session-runner.test.tsinpackages/core(passes, including multi-turn & tool continuation recovery tests, and assertingstateis undefined afterreplaySessionProjection)bun run typecheckpasses with 0 errorsmuse-spark-1.3-contributor-freeScreenshots / recordings
N/A (session runner fix)
Checklist