Skip to content

Background service resumes sessions still running on another server that shares the database #51213

Description

@alohaninja

Description

When a second OpenCode server shares the data directory, the managed service's startup recovery resumes sessions that the other server is still running. It marks their in-flight tool calls as aborted, adds "The server restarted while you were working", and starts its own step, so two servers drive one session.

Execution claims only store a timestamp, so the sweep can't tell a dead owner from a live one. restart.ts assumes "unregistered servers sharing the database never sweep", but their live claims still get swept by the service.

What I saw: a desktop client runs its own bundled opencode serve --port <n>, and the background service started while that server was mid-turn. When the original server's subagents finished, its next request failed with:

Anthropic Messages system updates cannot split a local tool call from its tool result

because the history now had the service's unresolved tool calls in it. The session stayed stuck until I sent another message. I didn't hit this on V1.

Plugins

None (isolated repro)

OpenCode version

2.0.16 (also seen with a 2.0.15 client-bundled server next to a 2.0.16 service)

Steps to reproduce

  1. Use fresh XDG_CONFIG_HOME / XDG_DATA_HOME / XDG_STATE_HOME / XDG_CACHE_HOME dirs and an Anthropic model.
  2. Start server A: opencode serve --port 4801.
  3. On A, prompt: "Call the subagent tool twice in parallel. Each subagent runs sleep 150 && echo done."
  4. While the subagents are running, start server B on the same dirs: opencode serve --service --port 4799.
  5. B marks A's subagent calls as errors, adds the restart notice, and starts new subagents.
  6. When A's subagents finish, A's next step fails with the error above.

Screenshot and/or share link

No response

Operating System

macOS 26 (Darwin 25.3.0, arm64)

Terminal

N/A (API-driven repro)

Activity

  1. gszep commented on Sep 28, 2026

    @gszep

    Thanks for the detailed report and #51215. I confirmed overlapping executions on a local 2.0.15-based build sharing its DB between an unregistered server and the managed service; each failed request preceded the other server’s tool completion. I am carrying both commits from #51215 (46 execution tests pass). There is also a separate deterministic lowering problem: an update followed by an available or repaired error result still throws. Repro in #51764; fixing that does not replace the live-owner recovery guard.

  2. nkoynov commented on Oct 7, 2026

    @nkoynov

    Hit this from T3 Code (2.0.22; restart.ts is unchanged in 2.0.24). The second server was started by an agent: in a thread served by T3's own opencode serve --hostname 127.0.0.1 --port N, the model ran opencode mcp list to debug an MCP login, and the CLI auto-started opencode serve --service. Its boot sweep resumed both sessions T3's server was running ("The server restarted while you were working" 0.15 s after start), and for 3h22m the two servers alternated steps. Each had a different MCP set, so one session got 437 full Code Mode catalog notices (~4 MB stored) and was auto-compacted.

    Repro on 2.0.24 (VM): T3 thread runs sleep 120; date >> runs.txt → opencode mcp list in a shell → the service marks the running shell call "Tool execution interrupted" while sleep keeps running, the resumed model starts the same command again (two sleep processes, two lines in runs.txt, two idle rows for one turn).

    Workaround until #51215 lands: {"disabled": true} in ~/.config/opencode/service.json. CLI commands then use a standalone serve --stdio, which never sweeps; same repro: one sleep, one line, no notice. Only opencode service start/restart, pair and serve --service still start the managed service.

  3. Starefossen commented on Oct 8, 2026

    @Starefossen

    Also on 2.0.24 (macOS arm64): two managed services with separate XDG state/config but a shared opencode.db. Service B's boot log shows Failed to drain Session for two sessions still running in A.

    Another effect: the resumed turn runs with B's tools, cwd and sandbox. A turn started in a sandboxed server can continue in an unsandboxed one.

    Source: restart.ts:192-231 only checks the local execution.active. Fix: record the claim owner (server id + heartbeat) and skip claims whose owner is alive.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions