Skip to content

service: "Managed service port ... already in use" when a healthy service is registered under a different XDG_STATE_HOME #56708

Open

Description

@Jerry-CodeHub

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/).

  1. Confirm it is running and healthy:
    • opencode service status → http://127.0.0.1:40900
    • curl -s -u "opencode:$PW" http://127.0.0.1:40900/api/info → {"version":"2.0.32","pid":<pid>,...} ($PW is the password from ~/.config/opencode/service.json)
  2. Run a client that uses a different state dir (any tool that exports XDG_STATE_HOME to its child processes can produce this):
    • XDG_STATE_HOME=$TMPDIR/other-state opencode service status → prints stopped while the service above is running
    • XDG_STATE_HOME=$TMPDIR/other-state opencode → prints Starting background server... and then the error above; exit code 1
  3. The same client without XDG_STATE_HOME reuses 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

Activity

  1. opencode-agent commented on Oct 18, 2026

    @opencode-agent
    Contributor

    Thanks for the detailed report. I can reproduce this on 2.0.32, and it still happens on the latest dev (60a5a6c).

    Steps:

    1. Start the service normally with opencode service start; opencode service status shows its URL.
    2. XDG_STATE_HOME=$(mktemp -d) opencode service status prints stopped, even though the service is still running.
    3. XDG_STATE_HOME=<same dir> opencode service start exits with code 1 and prints Managed service port … is already in use by another process. Configure another port ….
    4. The original service still answers /api/info with 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)
    #!/usr/bin/env bash
    # Repro for #56708: a healthy managed service registered under the default
    # XDG_STATE_HOME is invisible to a client using another XDG_STATE_HOME, which
    # then spawns a replacement on the same port and fails with "port already in use".
    # Usage: ./repro.sh                      (uses `opencode` from PATH)
    #        OC_DEV=/workspace/repo ./repro.sh  (runs the CLI from a source checkout)
    set -u
    if [ -n "${OC_DEV:-}" ]; then
      OC="bun --conditions=browser $OC_DEV/packages/cli/src/index.ts"
    else
      OC=opencode
    fi
    export HOME=$(mktemp -d)       # isolated home: shared ~/.config/opencode/service.json
    unset XDG_STATE_HOME XDG_CONFIG_HOME XDG_DATA_HOME
    PROJ=$(mktemp -d); cd "$PROJ"; trap 'pkill -f "serve --service" 2>/dev/null' EXIT
    # the background server is spawned as `bun <entry>` without flags; give it the TUI preload via bunfig
    [ -n "${OC_DEV:-}" ] && printf 'preload = ["%s"]\n' "$OC_DEV/packages/tui/node_modules/@opentui/solid/scripts/preload.js" > bunfig.toml
    echo "== version"; $OC --version
    echo "== start service from default environment"
    $OC service start 2>&1 | tail -3
    sleep 2
    echo "== status (default state dir)"; URL=$($OC service status 2>&1 | tail -1); echo "$URL"
    CFG=$(ls "$HOME"/.config/opencode*/service.json 2>/dev/null | head -1); echo "== config: $(cat "$CFG" 2>/dev/null)"
    OTHER=$(mktemp -d)
    echo "== status with XDG_STATE_HOME=$OTHER"
    XDG_STATE_HOME=$OTHER $OC service status 2>&1 | tail -3
    echo "== client with XDG_STATE_HOME=$OTHER"
    XDG_STATE_HOME=$OTHER timeout 90 $OC service start > out.txt 2>&1; echo "exit=$?"; grep -E "^(Error|Starting)" out.txt
    echo "== original service still listening? (200 = healthy; 401 = listening, password not in service.json on source builds)"; curl -s -o /dev/null -w "%{http_code}\n" -u "opencode:$(sed -n 's/.*"password": "\(.*\)".*/\1/p' "$CFG")" "$URL/api/info"

    Run it: bash repro.sh

  2. RobertJClose commented on Oct 19, 2026

    @RobertJClose

    I have a minor contribution to this conversation. My OpenCode log has many "Managed service port already in use" errors and the XDG_STATE_HOME variable 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.asar main bundle (Desktop 1.19.3):

    Object.assign(process.env, {
        ...shellEnv,
        OPENCODE_CLIENT: "desktop",
        XDG_STATE_HOME: process.env.XDG_STATE_HOME ?? userDataPath,  // app.getPath("userData")
    })
    

    So the desktop registers at %APPDATA%\ai.opencode.desktop\opencode\service.json
    while a CLI started from a shell resolves to ~\.local\state\opencode\service.json.

    Any opencode service restart or session list from 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 in Service.incumbent() would answer it — it just isn't reached on the "registration missing" path.

    Workaround with a caveat:
    Setting XDG_STATE_HOME as 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_HOME in packages/desktop/src/main/lifecycle/environment.ts, and shell is 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions