Skip to content

fix: preconfigured-client example and singleton client manager dispose recovery#27

Merged
ben-vargas merged 4 commits into
mainfrom
claude/cranky-galileo-69d651
Jun 11, 2026
Merged

fix: preconfigured-client example and singleton client manager dispose recovery#27
ben-vargas merged 4 commits into
mainfrom
claude/cranky-galileo-69d651

Conversation

@ben-vargas

Copy link
Copy Markdown
Owner

Summary

Fixes the client-options.ts example, which claimed to demonstrate the preconfigured-client pattern but did not, and fixes the underlying client-manager lifecycle issue uncovered while verifying it.

Example: step 2 never used the preconfigured client

createOpencode({ client }) hands the client to the process-wide singleton OpencodeClientManager, which step 1 had already initialized. The singleton warned and kept using step 1's client, so step 2's requests carried step 1's x-demo-source: client-options-example header while stdout misleadingly printed a success response.

  • Step 2 now passes an isolated manager: createOpencode({ clientManager: OpencodeClientManager.createInstance({ client }) }), with a comment explaining why.
  • Both clients wrap fetch to echo each outgoing request's x-demo-source header, so the demo output itself proves which client served each request.
  • Dropped the spurious await on createOpencodeClient() (synchronous in @opencode-ai/sdk/v2).
  • The example model is overridable via OPENCODE_MODEL (default unchanged, matching the other examples).

Client manager: dispose() bricked future providers

dispose() set isDisposed but left the static singleton pointing at the dead instance, so any later createOpencode() without an explicit clientManager received the disposed manager and threw Client manager has been disposed on first use. This also made the existing warnings' advice ("call dispose() first") incorrect.

  • dispose() now releases the singleton slot when the disposed manager is the singleton, so the next getInstance() builds a fresh manager. Non-singleton managers from createInstance() are unaffected.
  • The "already initialized" warnings now recommend the escape hatches that actually work: createInstance() with the clientManager setting, or resetInstance().
  • Added a regression test covering getInstance → getClient → dispose → getInstance with new options → getClient succeeds.

Also bumps to 3.0.5 with CHANGELOG entries, following the per-fix release convention.

Testing

  • npm run ci passes (typecheck, lint, 359 tests including the new regression test).
  • Live against opencode serve --hostname 127.0.0.1 --port 4096:
    • npx tsx examples/client-options.ts — header echo confirms step 1's requests carry x-demo-source: client-options-example and step 2's carry x-demo-source: preconfigured-client-example.
    • Dispose-recovery scenario: provider → generateText → dispose → new createOpencode({ baseUrl }) → generateText succeeds (previously threw Client manager has been disposed).
    • Old broken pattern (createOpencode({ client }) after singleton init) emits the updated warning with the corrected guidance.

…red client

Step 2 handed its preconfigured client to the process-wide singleton client
manager, which step 1 had already initialized, so the client was ignored and
step 2's requests flowed through step 1's client while the demo reported
success. Pass an isolated manager via createInstance() instead, echo each
request's x-demo-source header so the output proves which client served it,
drop the spurious await on the synchronous v2 createOpencodeClient(), and
allow overriding the model via OPENCODE_MODEL.
dispose() set isDisposed but left the static singleton pointing at the dead
instance, so every later createOpencode() without an explicit clientManager
received the disposed manager and threw "Client manager has been disposed"
on first use. Release the singleton slot in dispose() so getInstance()
builds a fresh manager, and update the already-initialized warnings to
recommend the escape hatches that actually work (createInstance() with the
clientManager setting, or resetInstance()).
@ben-vargas
ben-vargas merged commit 62ad134 into main Jun 11, 2026
3 checks passed
@ben-vargas
ben-vargas deleted the claude/cranky-galileo-69d651 branch June 11, 2026 12:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant