fix(daemon): let opencode choose its own port when spawning runtimes#28
Merged
Conversation
Pass `port: 0` to `createOpencode` in `OpencodeRegistry.ensureStarted` so multiple concurrent instances stop colliding on 4096.
chenxin-yan
approved these changes
Apr 22, 2026
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
The daemon could register N instances but only ever run one at a time. Starting a second instance failed with:
The TUI hit this when switching models, because that path triggers a fresh opencode spawn while the existing one is still holding port 4096.
Root cause
OpencodeRegistry.ensureStartedinpackages/daemon/src/opencode.tscalledcreateOpencode()with no options.@opencode-ai/sdk@1.3.13'screateOpencodeServerthen filled in its hardcoded default port:So every spawn asked the
opencodebinary to bind exactly127.0.0.1:4096. The second concurrent spawn lost the race and exited withEADDRINUSE.The fix
One change:
createOpencode()→createOpencode({ port: 0 }).--port=<nonzero>— binds exactly that port; exits 1 onEADDRINUSE(no fallback).--port=0— tries 4096 first, then falls back to an OS ephemeral port if 4096 is busy. Supports arbitrarily many concurrent instances.Verification
Reproduced against
@opencode-ai/sdk@1.3.13and the bundledopencodebinary:createOpencode()): Started two registered instances viaralph daemon instance start. First bound to 4096; second failed with the exact error above.createOpencode({ port: 0 })): Same two-instance drive. First bound to 4096; second bound to an ephemeral port (61264 in one run, 53157 in another). Both instances ran concurrently with no collision.opencode serve --port=0alone binds 4096; running a second one concurrently lands on an ephemeral port. Confirms the fallback is inside the opencode binary, not the SDK.Test plan
createOpencode()→ "Failed to start server on port 4096" on second instancecreateOpencode({ port: 0 })→ two instances run concurrently on distinct portsopencode serve --port=0invocation that fallback lives in the opencode binaryFiles touched
packages/daemon/src/opencode.ts—OpencodeRegistry.ensureStartednow passes{ port: 0 }tocreateOpencode(+ a biome-driven reformat of the surrounding.thencallback).