Skip to content

service: background server boots the user home directory as a location #49209

Open

Description

@alvin-qh

Summary

The background server (opencode service) comes up with the user's home directory as its working directory and boots that directory as a location, even though no client asked for it. Every location gets its own MCP servers and plugin instances, so an unused "home" project is started on every server start.

Environment

  • opencode version: 2.0.3
  • OS: Windows NT 10.0.26200 (win32, x64)
  • Terminal: TERM=xterm-256color, TERM_PROGRAM/COLORTERM unset
  • Shell: cmd.exe
  • Install/channel: global Bun install (@opencode/cli), channel latest
  • Active plugins: ./plugins/claude-mem (server plugin), ./plugins/plugin-info (cli plugin), both local directories under ~/.config/opencode/plugins/

Reproduction

  1. From any directory (I ran it with cwd = my project directory), run opencode service restart.
  2. Grep the server log for location boots:
    rg "location services booted" ~/.local/share/opencode/log/opencode.log
  3. Observe that the server boots the user's home directory as a location before/alongside the requested project.

Expected Behavior

The background server should boot only locations that a client actually requests (or at minimum the caller's project directory). Starting the service should not create a location for the user's home directory.

Actual Behavior

Immediately after opencode service restart, the log shows two location boots:

18:22:08Z location services booted directory="C:\\Users\\<user>"
18:22:10Z location services booted directory="C:\\Users\\<user>\\Workspace\\Work\\<project>"

The second is my session's project. The first is the home directory, which nothing requested.

Impact observed:

  • A full set of MCP servers is spawned for the home location (8 configured servers, including serena, which starts a whole serena.exe process tree plus its own web-dashboard port).
  • Global plugins are instantiated for the home location as well. Because ctx.event.subscribe is the server-wide stream, that extra instance also handles other locations' sessions: I measured a single session.text.ended event being delivered to 3 plugin instances, producing duplicate observation posts.
  • Extra language-server / child processes and one wasted dashboard port per server start.

Additional Context

  • The service is spawned detached and its cwd is not inherited from the caller — the restart above was issued with cwd = the project directory, yet home was booted. A private server started with opencode api --standalone boots its own cwd as the location (verified: it booted the temporary directory it was started from), which suggests the service is nominally started with $HOME as its cwd.
  • Registration is written to ~/.local/state/opencode/service.json as { id, version, url, pid, password } (password redacted here).
  • No autostart is involved: there is no scheduled task or startup shortcut for opencode; the service is started on demand by the CLI and then stays resident.
  • No configuration field seems to control the server's initial location, so there is no user-side workaround. A service option (or not booting any location until a client asks) would resolve it.
  • Related, but distinct: when the same project is reachable through two path spellings (a symlinked path and the real path), opencode registers two locations with two sets of instances. The home boot is a third, automatic one that appears even with a single spelling.

Activity

  1. github-actions commented on Sep 15, 2026

    @github-actions
    Contributor

    This issue might be a duplicate of an existing issue. Please check:

  2. opencodejr commented on Oct 28, 2026

    @opencodejr

    Thanks for the detailed report. I can reproduce the core of this on 2.0.3, and it still happens on the latest dev (950111f).

    What I saw (Linux, server API):

    • I started the background server (serve --service, which opencode service start launches) from a project directory. Its working directory was still the home directory: in service mode it always switches to $HOME.
    • Starting it boots nothing, and a request naming the project boots only the project.
    • One request that names no location (for example GET /api/agent without location[directory] or the x-opencode-directory header) makes the server fall back to its own working directory. It logs location services booted directory=<home> and starts a full location for home, with its MCP servers and plugins.

    One thing I couldn't pin down: on Linux, the bundled CLI commands and the TUI attached to the service only booted the project. So something in your setup (the Windows client, or possibly a plugin calling the SDK without a location) is sending the request without a location. If you can find which request it is, for example from the server log just before the home boot, that would help.

    The script below reproduces it against the service API.

    Closely related:

    Reproduction script (repro.sh)
    #!/usr/bin/env bash
    # Reproduce #49209: the background server (`serve --service`, what `opencode service start` spawns)
    # switches its cwd to $HOME whatever the caller's cwd is, and any request that does not name a
    # location boots $HOME as a full location (MCP servers, plugins).
    # Usage: bash repro.sh                       (uses `opencode` from PATH)
    #        OC_DEV=/workspace/repo bash repro.sh  (runs the CLI from a source checkout)
    set -u
    export HOME=$(mktemp -d /tmp/opencode/home.XXXX)
    unset XDG_DATA_HOME XDG_STATE_HOME XDG_CONFIG_HOME XDG_CACHE_HOME
    oc() {
      if [ -n "${OC_DEV:-}" ]; then
        bun --conditions=browser --preload="$OC_DEV/packages/tui/node_modules/@opentui/solid/scripts/preload.js" "$OC_DEV/packages/cli/src/index.ts" "$@"
      else opencode "$@"; fi
    }
    PROJ=$HOME/work/project; mkdir -p "$PROJ"; cd "$PROJ"; git init -q .
    PORT=$((49500 + RANDOM % 400))
    echo "version: $(oc --version)  HOME=$HOME  caller cwd=$PWD"
    oc serve --service --port $PORT >$HOME/serve.log 2>&1 &
    SPID=$!
    for i in $(seq 1 120); do curl -s -o /dev/null http://127.0.0.1:$PORT/ && break; sleep 0.5; done; sleep 2
    REG=$(ls $HOME/.local/state/opencode/service*.json $HOME/.config/opencode/service*.json 2>/dev/null | head -1)
    PW=$(python3 -c "import json;print(json.load(open('$REG'))['password'])")
    URL=http://127.0.0.1:$PORT
    SRV=$(pgrep -f -- "serve --service --port $PORT" | while read p; do [ "$(readlink /proc/$p/cwd)" ] && echo $p; done | tail -1)
    echo "service $URL server pid=$SRV cwd=$(readlink /proc/$SRV/cwd)"
    LOG() { grep -rh "location services booted" $HOME/.local/share/opencode/log/ 2>/dev/null | grep -o 'directory=[^ ]*'; }
    echo "--- boots right after start:"; LOG
    curl -s -o /dev/null -u "opencode:$PW" "$URL/api/agent?location%5Bdirectory%5D=$PROJ"
    sleep 1; echo "--- boots after a request naming the project:"; LOG
    echo "GET /api/agent (no location) -> $(curl -s -o /dev/null -w '%{http_code}' -u "opencode:$PW" $URL/api/agent)"
    sleep 2; echo "--- boots after one directory-less request:"; LOG
    kill $SPID 2>/dev/null; pkill -f -- "--port $PORT" 2>/dev/null; wait 2>/dev/null

    Run it: bash repro.sh

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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions