#52452·opencode

session: tool aborted by a background-service restart leaves unpaired tool_calls, producing 400 on resume

Author: nowzyccCreated Oct 1, 2026Updated Oct 2, 2026

Summary

When a tool call is aborted because the command it runs stops/restarts the OpenCode background service, the session persists an assistant message containing tool_calls with no matching tool result. After the restart, the resume path dispatches the next step before that result is paired, so a strict OpenAI-compatible provider rejects the whole history with HTTP 400 invalid_request_error. Retries resend the same malformed history.

Environment

  • opencode version: 2.0.21
  • OS: macOS (Darwin 27.0.0, arm64)
  • Terminal: OpenChamber desktop app (terminal environment variables not set)
  • Shell: /bin/zsh
  • Install/channel: latest (release build, not local/dev)
  • Active plugins: opencode.browser (built-in), local openchamber-agent-tool

Reproduction

  1. In a session, let the agent run a shell tool whose command stops/restarts the managed background service, e.g. opencode service unset env <VAR> (any command that restarts or kills the service).
  2. The restart interrupts the in-flight tool. It is persisted with status error, {"type":"aborted","message":"Tool execution interrupted: shell"}, and a synthetic "The server restarted while you were working. Continue from where you left off..." message is inserted.
  3. On resume, a new step is dispatched to the provider while the aborted tool's result is not yet paired with its tool_call_id.
  4. The request therefore contains an assistant message with tool_calls but no corresponding tool message for that call.
  5. The request fails with 400; retries keep resending the same malformed history.

Expected Behavior

Every assistant tool_calls message sent to a provider must be immediately followed by a tool message for each tool_call_id. An aborted/interrupted call should get a synthetic error/placeholder result, or the dangling call should be dropped. Resuming after a service restart must never emit an unpaired tool_calls message.

Actual Behavior

The provider returns:

An assistant message with 'tool_calls' must be followed by tool messages responding to each 'tool_call_id'. (insufficient tool messages following tool_calls message)

In the application this surfaces as AI.Error.InvalidRequest and the session drain fails; the user sees "opencode failed to send message".

Additional Context

Reconstructed from the local session DB (opencode 2.0.21) — one session, all times UTC, IDs/prompts removed:

  • seq A: assistant message issues a shell tool call. The command restarts the service; the tool is persisted as error / {"type":"aborted"}.
  • seq B (+~5s): synthetic "The server restarted while you were working..." message inserted.
  • seq C: assistant message issues the next tool call; the tool starts at time T.
  • A provider request is created at T + ~0.3s and fails — i.e. while that tool was still running and before its result was ever written.
  • A later retry succeeds; the session recovers on its own (or after the user sends a new message).

The same pattern repeated three times in this one session, each time immediately after a service restart (each triggered by a tool command that restarted the service). Sessions without an interruption did not reproduce it. Only strict OpenAI-compatible endpoints trip on it; providers that tolerate orphaned calls keep working.

Workaround: avoid running commands that restart/stop the managed background service inside a running turn.

Related issues: #44852, #37364, #51213, #49911 (withdrawn), #22808 (auto-closed).