Repository navigation
cli: background service startup reports generic timeout, hiding port-in-use root cause #49009
Description
Summary
When the managed background service cannot bind its port, every spawned serve --service contender fails with a clear, actionable error ("port already in use by another process") — but the client only surfaces Error: Timed out waiting for the background service to start. The actual cause is only visible by digging through the log file.
Environment
- opencode version: 2.0.3
- OS: Darwin 27.0.0 (macOS, arm64)
- Terminal: ghostty (
TERM=xterm-ghostty,COLORTERM=truecolor) - Shell: fish
- Install/channel: latest (bun global install)
- Active plugins: none found in server config (
oh-my-openagent:tuiandwhich-keyTUI plugins enabled in client state)
Reproduction
- Occupy the managed service port with any process that prevents
bind()— e.g.python3 -m http.server <port>or, as in my real case, any lingering ESTABLISHED connection whose local port matches. - Run
opencode service start(or plainopencode, which auto-starts the service). - Wait through the retry loop (~2 minutes of silent respawns).
Observed on macOS where a stale Tailscale-loopback connection (127.0.0.1:49383 -> 127.0.0.1:49374, ESTABLISHED, no LISTEN socket anywhere) made the port unbindable even with SO_REUSEADDR — verified with a manual Python bind(). Nothing showed up under lsof -sTCP:LISTEN, so even the standard "find what listens on the port" advice failed; only the opencode log revealed the truth.
Expected Behavior
The final error surfaces the underlying cause from the spawn attempts, e.g.:
Error: Timed out waiting for the background service to start
cause: Managed service port 49374 on 127.0.0.1 is already in use by another process.
Configure another port with `opencode service set port <port>` and start the service again.
Actual Behavior
Only this is shown:
Starting background server...
ERROR (#1): Error: Timed out waiting for the background service to start
Meanwhile the log accumulates dozens of entries with the real, actionable cause:
level=ERROR message="cli process failed" cause="Cause([Fail(Error: Managed service port 49374 on 127.0.0.1 is already in use by another process. Configure another port with `opencode service set port <port>` and start the service again. ...)])" args="[\"serve\",\"--service\"]" role=cli
Root Cause (code pointers)
packages/cli/src/server-process.ts:149— the spawnedserve --serviceprocess already detectsEADDRINUSEand produces the helpful message, including theservice set porthint.packages/client/src/effect/service.ts— inensure, a finished contender's failure is only returned whencontenders.size === 0(line 122). Because the loop keeps one candidate plus one lock probe in flight, each failure arrives while another contender is still pending, so the specific error is dropped. Once the attempt schedule is exhausted, line 138 throws the genericTimed out waiting for the background service to startwith nocause.- Same pattern in
packages/client/src/promise/service.ts:59.
Additional Context
- 100% reproducible while the managed port is unbindable; resolves immediately once the port is freed.
- Workarounds:
opencode --standalone, oropencode service set port <free-port>thenopencode service start. - Suggested fix: track the most recent contender failure in the ensure loop and attach it as
causeto the final timeout error. - Secondary consideration: the managed service binds a fixed port inside the OS ephemeral range (49374 on macOS), so ordinary outbound connections from unrelated apps can coincidentally squat on it (as happened here). Detecting "port busy with no recognizable incumbent" and auto-selecting a replacement port — or at least hinting at it in the error — would make this self-healing.
Activity
github-actions commentedon Sep 14, 2026
This issue might be a duplicate of existing issues. Please check:
serve --serviceattempts when port 49374 is in use, never surfacing the underlying port-conflict erroropencode web/opencode servecommands (related, different code path)opencode serveshows "Unexpected error" with no details when startup fails #48784:opencode serveshows "Unexpected error" with no details on startup failure (related, different code path)If your issue is distinct (e.g. the specific client-side
ensureloop dropping contender errors), please clarify the difference from #41696 so maintainers can track separately.sghng commentedon Sep 14, 2026
Duplicate of #41696. Copied the release-channel (v2.0.3) reproduction, code-pointers, and stale-connection diagnostics into a comment there.