Repository navigation
Description
Summary
When a managed service is already running but was registered under a different XDG_STATE_HOME than the client's, the client cannot discover it, decides the service is missing, spawns a replacement on the same configured port, and fails with a misleading error:
Error: Server process exited with code 1
Error: Managed service port 40900 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.
The running service is healthy (GET /api/info with the password from the shared ~/.config/opencode/service.json returns 200), so this is a discovery problem, not a port problem. Changing the port would not help either, since both environments share the same config file and would simply contend over the new port. Other clients (TUI, VS Code extension, desktop) fail to start until the mismatched service process is found and killed by hand.
Environment
- opencode version: 2.0.32
- OS: macOS 26.4.1 (Darwin 25.4.0, arm64)
- Terminal: xterm-256color (not terminal-specific; also occurs via the VS Code extension)
- Shell: /bin/zsh
- Install/channel: Homebrew tap
anomalyco/tap/opencode-v2(release) - Active plugins:
@warp-dot-dev/opencode-warp; local plugin file~/.config/opencode/plugins/otty-integration.js
Reproduction
Precondition: a managed service is running, started from a normal environment and registered in the default state dir (~/.local/state/opencode/).
- Confirm it is running and healthy:
opencode service status→http://127.0.0.1:40900curl -s -u "opencode:$PW" http://127.0.0.1:40900/api/info→{"version":"2.0.32","pid":<pid>,...}($PWis the password from~/.config/opencode/service.json)
- Run a client that uses a different state dir (any tool that exports
XDG_STATE_HOMEto its child processes can produce this):XDG_STATE_HOME=$TMPDIR/other-state opencode service status→ printsstoppedwhile the service above is runningXDG_STATE_HOME=$TMPDIR/other-state opencode→ printsStarting background server...and then the error above; exit code 1
- The same client without
XDG_STATE_HOMEreuses the running service with no error.
Reproducibility: deterministic on 2.0.32 (100%), including the trace below. Replace 40900 with your configured port.
Expected Behavior
Either:
- the client finds and reuses the healthy service listening on the configured port (credentials are available in the shared config regardless of state dir), or
- if reuse is intentionally scoped to the state directory, fail with an accurate message such as "a service is running but registered under a different state directory", instead of "port already in use" with a port-change suggestion.
Actual Behavior
Starting background server...
Error: Server process exited with code 1
Error: Managed service port 40900 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.
at <anonymous> (/$bunfs/root/chunk-680z5ykb.js:2:8925) {
[cause]: ServeError:
at q (/$bunfs/root/chunk-2388pc86.js:52:7706)
at emitListenErrorNextTick (node:_http_server:134:12)
[cause]: Error: Failed to start server. Is port 40900 in use?
at serve (unknown)
}
}
at map (unknown:1:11)
at reap (/$bunfs/root/chunk-3wwjbdge.js:2:1203)
at service.ensure (/$bunfs/root/chunk-4yp3z0hm.js:3:690)
at cli.server-connection.resolve (/$bunfs/root/chunk-xxrwvhw5.js:96:6563)
The spawned replacement waits ~15 s for an "existing listener" to exit before giving up; the server log then records, repeatedly while the mismatch persists (~every 30–60 s in the observed incident):
level=ERROR message="cli process failed" cause="Cause([Fail(Error: Managed service port 40900 on 127.0.0.1 is already in use by another process. ..."
Additional Context
- Real-world trigger: another opencode-derived app (Kilo) was running with
XDG_STATE_HOME=~/Library/Application Support/Kiloin its process environment; the OpenCode desktop app and its bundledopencode-cli serve --servicechildren inherited that environment, while terminal and VS Code clients used the default~/.local/state/opencode. Both sides read the same port from~/.config/opencode/service.json, so each kept spawning a replacement for a service it could not see. (In the observed incident the alternate state dir stayed empty, leaving the running service undiscoverable from both environments.) opencode service restartfrom the affected environment fails the same way.- Workaround that resolved it: quit the app carrying the stray
XDG_STATE_HOME, kill strayserve --serviceprocesses, then start the service once from a clean environment (unset XDG_STATE_HOMEin affected shells). - Client logic that looks relevant:
reap()/shouldRecruit()/service.ensure(cli.server-connection.resolve) treat "no registration in my state dir" as "service missing" without probing the configured port, and the replacement spawn's takeover wait cannot identify or take over a listener registered in a different state dir. - Related: service: relaunch fails with "Managed service port already in use" when previous background service has not exited #54267 (relaunch fails when a previous service has not exited), Managed background service startup can take minutes and saturates the disk; the CLI's short health check turns it into a spawn/retry storm #54810 (startup storm caused by the short health check), [FEATURE] Desktop: persist sidecar server URL/credentials to disk (XDG_STATE_HOME) so companion tools can auto-discover #47206 (sidecar server discovery / credentials persistence).
Activity
opencode-agent commentedon Oct 18, 2026
Thanks for the detailed report. I can reproduce this on 2.0.32, and it still happens on the latest
dev(60a5a6c).Steps:
opencode service start;opencode service statusshows its URL.XDG_STATE_HOME=$(mktemp -d) opencode service statusprintsstopped, even though the service is still running.XDG_STATE_HOME=<same dir> opencode service startexits with code 1 and printsManaged service port … is already in use by another process. Configure another port …./api/infowith your configured password.The script below reproduces it. It looks like the client only looks for a registration under its own state directory, and never probes the configured port before starting a replacement. Related: #56367 (same error with container and host instances sharing a network, likely the same root cause) and #54267 (same error when a previous service hasn't exited yet).
Reproduction script (repro.sh)
Run it:
bash repro.shRobertJClose commentedon Oct 19, 2026
I have a minor contribution to this conversation. My OpenCode log has many "Managed service port already in use" errors and the
XDG_STATE_HOMEvariable is relevant to it.Windows 11 Version 25H2 Build 26200.9711
OpenCode Desktop v1.19.3
The difference in my case is that on Windows the two state directories disagree by default — no stray XDG_STATE_HOME from another app is involved.
The desktop app sets the variable itself. From the shipped
app.asarmain bundle (Desktop 1.19.3):So the desktop registers at
%APPDATA%\ai.opencode.desktop\opencode\service.jsonwhile a CLI started from a shell resolves to
~\.local\state\opencode\service.json.Any
opencode service restartorsession listfrom Git Bash while the desktop app is open then fails with the error in this issue, on the managed default port 49374 (0xC0DE).I hit it 8 times in one day. Both clients read the same
~/.config/opencode/service.json, so the password matches and the incumbent probe inService.incumbent()would answer it — it just isn't reached on the "registration missing" path.Workaround with a caveat:
Setting
XDG_STATE_HOMEas a Windows user environment variable (so Explorer-launched Electron inherits it) makes both sides agree, and my log shows no failures after the change.But dev HEAD now does
if (!shellEnv?.XDG_STATE_HOME) delete process.env.XDG_STATE_HOMEinpackages/desktop/src/main/lifecycle/environment.ts, andshellis null on Windows, so the desktop will drop the variable and fall back to~/.local/state. The two paths converge on their own there, so I would treat the env variable workaround as temporary fix rather than a recommendation.