Skip to content

service: restart auto-resumes suspended sessions and executes pending tasks without user consent #59635

Open

Description

@ZhukovLabs

Summary

When the shared background service (re)starts, opencode injects a synthetic restart notice into every session that was left unfinished (e.g. an in-flight LLM call when the previous service died or hung), and the agent loop auto-continues without any user action. In our case a session that had been suspended for ~5 hours (hung provider call) auto-resumed twice — once per service start — and executed a destructive system task (elevated UAC script that uninstalled software and deleted engine installations) 7 hours after the user's last interaction with that session. It also killed a process that a different, active session was working with.

Environment

  • opencode version: 2.2.0 (latest channel at time of filing)
  • OS: Windows 11 Pro 10.0.26100 (win32 x64)
  • Terminal: Orca (TERM_PROGRAM=Orca, TERM=xterm-256color, COLORTERM=truecolor)
  • Shell: PowerShell 7 (pwsh) for opencode shell tool; COMSPEC=cmd.exe
  • Install/channel: npm global @opencode/cli@2.2.0, release
  • Active plugins: billion-context, opencode-zai-usage, universal-modder
  • Provider: z.ai coding plan (glm-5.3-max)

Reproduction

  1. Start a session and give it a multi-step task with side effects (e.g. "uninstall X completely, don't miss anything").
  2. While the agent is mid-generation (LLM call in flight), let the provider call hang or kill the shared service process. The session stays in a suspended/unfinished state. In our case one assistant message stayed in-flight for ~4h48m (created 10:06:18, completed 14:56:24 — no timeout fired).
  3. Hours later, start any opencode TUI (this starts a new shared service).
  4. The service injects a synthetic user-role message into the suspended session:
    {"metadata":{"notice":"restart"},"text":"The server restarted while you were working. Continue from where you left off without repeating completed work.","description":"Continuing after restart"}
  5. The agent auto-continues the task with full destructive context, executing shell commands (including elevation via Start-Process -Verb RunAs → UAC) without the user ever touching that session.

Observed twice in one day, for multiple sessions: an orchestrator session and an unrelated stalled research subagent both auto-continued on both service starts, confirming this is systemic behavior on service start, not a one-off.

Expected Behavior

A session the user has not interacted with for hours should never resume executing on its own. Restart recovery should at most mark the session as interrupted and wait for an explicit user "continue", or ask for confirmation before continuing — especially before running shell commands with side effects. Auto-continuation of the current turn immediately after a crash-restart may be desirable UX, but silently finishing an hours-old task is dangerous.

Actual Behavior

Timeline (local time = UTC+3; timestamps from opencode.db session_message rows and log/opencode.log):

  • 09:50 — user starts session A (task: fully uninstall Epic Games Launcher / Epic Online Services / Unreal Engine). Agent works 09:51–10:06.
  • 10:06:18 — session A's assistant message created; the provider call hangs and the session stays suspended mid-run. The user considers that session long finished and works in other sessions/projects.
  • 14:56:19 — shared service start feat: compact and other improvements #1 (previous service had died). Synthetic restart notice injected into session A → agent auto-continues: starts searching disks for engine installations.
  • 15:48:40 — shared service start Roadmap & Existing Issues #2 (user launched a TUI in a terminal). Second synthetic restart notice → agent auto-continues again.
  • 15:50:00–15:50:25 — session A writes a removal script and launches it elevated (Start-Process pwsh -Verb RunAs, UAC approved by a user who was mid-context-switch). The script kills Epic/Unreal processes — including an UnrealEditor instance that a different, active session was driving via MCP at that moment — uninstalls Epic Online Services (msiexec /x), deletes engine folders, and scrubs registry entries.
  • 15:52:55 — session A reports success. The user had not touched session A since 10:06 and did not ask for it to continue.

A stalled research subagent session (no user interaction since 10:08) auto-continued at 14:56:19 and 15:48:40 the same way, spending tokens and running web requests on its own.

Additional Context

Activity

  1. opencode-agent commented on Oct 27, 2026

    @opencode-agent
    Contributor

    Thanks for the detailed timeline and for tracing this through the database and logs. That's a serious outcome, and the write-up makes it very clear.

    I think this is a duplicate of #56032. That issue reports the same thing: when the background service starts, it resumes every suspended session with no user action. It asks for a way to keep those sessions parked until you continue them yourself. I checked the current code, and restart recovery (packages/core/src/session/execution/restart.ts) resumes every session whose turn didn't finish, each time the service starts. The only limit is how many times one turn may be resumed. There is no age check, no confirmation step and no setting to turn it off. That's why a session left alone for about 5 hours resumed on both service starts.

    Possible duplicates and related issues, most likely first:

    The provider call that hung for about 4.8 hours without timing out is a different problem, and none of these issues cover it. If you can, please open a separate issue for it with the provider and model details and the relevant log lines.

    The bot will close this issue as a duplicate of #56032 in 1 day unless you reply explaining how your problem is different.

  2. KonstantinChizhov commented on Oct 29, 2026

    @KonstantinChizhov

    Same issue. Old interrupted task resumes on opencode startup in background. No way to stop it except to kill opencode process. Reproduced in version 2.2.1

  3. YegorMy commented on Oct 30, 2026

    @YegorMy

    A related case that is a correctness problem, not only a consent one: when two OpenCode servers share one data directory, the service's boot recovery can resume sessions that are still running in the other server, so the same session ends up with two executors.

    Setup

    We run a small local orchestrator (our own job runner). It starts a dedicated server with opencode serve --hostname 127.0.0.1 --port <fixed> and drives agent sessions through the HTTP API: it creates sessions, prompts them, and lets them spawn subagents, including background: true ones. On the same machine and user account, the regular CLI/TUI uses the background service (opencode serve --service). Both servers use the default data directory, so they share one opencode.db.

    • macOS, OpenCode 2.2.0 installed. The orchestrator's server was started earlier and still runs 2.1.0.
    • The analysis below comes from reading the shipped 2.2.0 bundle and the DB state. We have not deliberately triggered a service boot to watch a duplicate run.

    What the 2.2.0 code does

    • ServerProcess.start runs SessionRestart.resumeSuspendedSessions only for service mode (the onListen path). A plain opencode serve never runs it.
    • resumeSuspendedSessions builds its target set from:
      • SessionStore.listSuspended(), i.e. sessions with time_suspended IS NOT NULL AND parent_id IS NULL;
      • the children of persisted job.background/* records with status: "running" and recovery.kind: "subagent".
    • It removes only the sessions that are active in the booting process (its own drain map). Each remaining target gets countResume (cap 10), the synthetic "The server restarted while you were working…" notice and resume(...).
    • The claims (session_v2.time_suspended) and the job.background/* records live in the shared DB and carry no owner. A session currently executing in the other server looks exactly like a crashed one. Restarting the background service while the orchestrator's server runs work would resume those live roots a second time.

    We also had stale entries that recovery would have revived days later. A disk-full (ENOSPC) event stopped several sessions mid-step. Their claims stayed set, and one background-subagent job stayed status: "running" with no terminal record. The next service boot would have woken those agents as writers in their old working trees.

    No native way to settle such a session

    • POST /api/session/:id/interrupt on an inactive session is the documented idle no-op. It releases neither the claim nor the background job.
    • Archiving doesn't help: listSuspended and job recovery don't filter on time_archived.
    • There is no public API to cancel or complete a persisted background job.
    • There is no config to turn off boot resume. SessionRestart is built with defaults (maxAttempts 10).
    • The only route we found is DELETE /api/session/:id. It works, but it destroys the history.

    Suggestions

    1. Ownership-scoped recovery. Record who holds a claim, e.g. a server instance id or pid plus a boot nonce. Resume only claims left by a previous incarnation of the same server identity. Never resume a session whose claiming process is still alive, or that another server reports as active.
    2. A native settle operation for an inactive session or background job that releases the claim and marks the job cancelled or failed with a reason, without deleting history.
    3. A switch to disable automatic resume on boot, or require an explicit opt-in. This matches the consent concern in this issue.

    Our current workaround is to avoid restarting the background service while the orchestrator's server has work in flight, and to delete the orphaned sessions (after exporting them) when needed. We are also considering a separate data directory for the orchestrator's server, but that also separates provider logins, so per-instance ownership upstream would be the cleaner fix.

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