test: in-process e2e via SDK server; fix recovery race with echoed chat.message#12
Merged
Conversation
…at.message Add test/e2e-server.test.ts: boots a real headless OpenCode server (createOpencodeServer) with a fake OpenAI provider, drives it through the SDK client, and observes plugin decisions via the SSE event stream. Covers status-path fallback, retry_on_patterns, cascade fallback, and recovery. The recovery e2e exposed a real bug: OpenCode invokes the chat.message hook for the plugin's own retry prompt. Since pendingModel was set only after promptAsync resolved, that echoed hook call was treated as a manual model switch, overwriting originalModel with the fallback and silently disabling recover_original_model in the live runtime. Set pendingModel/awaitingModel before prompting (and roll back if the retry is not accepted). Also: chat.message now backfills state models via getState when a model is present (session.created carries no model in the live runtime), and cascade-model uses 400 (OpenCode self-retries 429/503 before emitting session.error, masking plugin-driven cascade). New unit test reproduces the echoed-prompt runtime behavior. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.
Summary
test/e2e-server.test.ts): boots a real headless OpenCode server viacreateOpencodeServer, drives it through the SDK client, and observes plugin decisions through the SSE event stream (session.error,tui.toast.show,message.updated) plus the fake provider's call log. Fully isolated per test (temp dirs,XDG_DATA_HOME/XDG_CONFIG_HOMEoverrides,port: 0).recover_original_model, feat: recover to original model after cooldown expires #5) never fired in the live runtime. OpenCode invokes thechat.messagehook for the plugin's own retry prompt;pendingModelwas set only afterpromptAsyncresolved, so the echoed hook call was treated as a manual model switch —originalModelwas overwritten with the fallback and recovery was silently disabled. Fix: setpendingModel/awaitingModelbefore prompting, roll back if the retry is not accepted.chat.messagenow backfills state models viagetStatewhen a model is present (in the live runtimesession.createdcarries no model, unlike the unit mocks).cascade-modelin the fake provider switched to status 400: OpenCode self-retries 429/503 with its own backoff before emittingsession.error, masking plugin-driven cascade.Runtime findings validated by e2e
session.errorreally carries no model inproperties— the plugin correctly falls back tostate.currentModel(mock/runtime divergence confirmed harmless).retry_on_patterns(status-independent path), cascade fallback, and recovery all proven against a live server.Testing
bun run typecheck— cleanbun test test/index.test.ts— 59 passbun test test/e2e-server.test.ts— 4 pass (~25s)bun test test/e2e.test.ts— 7 pass (untouched CLI e2e)🤖 Generated with Claude Code