Skip to content

Background service resumes opencode run --standalone sessions after the run was killed, without its permissions or step limit (runaway: 1,660 steps, ~$24) #55076

Open

Description

@user753

Description

Description

A session started by opencode run --standalone is resumed by the background service (opencode serve --service) after the run process is gone. The resumed turn runs without the config the run was started with, has no age limit, and is resumed again on every later service start. In my case two headless code-review runs that were killed on Oct 10 were resumed 30 hours later and ran unattended for ~5.5 hours, 1,660 steps in total, costing ~$24 on OpenRouter. I only noticed from the bill.

Related: #51213 (service resumes sessions a live server is still running). Its fix #51215 skips claims whose owner is alive, but says dead owners "behave as before", so it doesn't cover this case: here the owner was dead.

Problems, in the order they happened:

  1. A killed standalone run leaves its session resumable. The run client ended mid-turn (log: InterruptError … POST /api/experimental/session/<id>/wait … http.status=499). The session was never marked as stopped and had no idle event, so it stayed "working" in the shared opencode.db.
  2. The shared service resumes sessions it never owned. The session ran on the run's private --standalone server, but the background service, which shares the data directory, picked it up on its next start and added "The server restarted while you were working. Continue from where you left off without repeating completed work." A standalone run's session should end with that run.
  3. The resumed turn drops the run's config. The run was started with OPENCODE_CONFIG_CONTENT setting agent.build.steps: 25 and a bash allowlist ("*": "deny", only git log *, git show *, rg * and ls * allowed). The resumed sessions ran 861 and 799 steps, and shell commands the allowlist would deny completed: cd <repo> && git show … (105×), git -C <repo> … (112×), sed -n … (9×), git grep … (10×). So a session started with restricted permissions continued with default ones. Anything the original process set up (env, wrapper timeout, an OS sandbox around the CLI) is gone too.
  4. There's no age limit, and it happens on every restart. Resumed 30 h after the run died (Oct 10 → Oct 11), resumed again on the next service start (Oct 12; that attempt failed with Failed to drain Session … database is locked), and on Oct 13 a service start resumed two more killed standalone sessions from Oct 12.
  5. Nothing visible happens. For headless runs nobody is attached, so a resumed session runs unattended until the service stops.
  6. (Minor, possibly a separate issue) There's no loop protection: the resumed model re-read the same files up to ~50 times each, the context grew to ~940k tokens, it was auto-compacted, and the loop continued.

Expected:

  • When a --standalone run exits or is killed, its in-flight turn ends as stopped, not suspended. At minimum, the shared service never resumes sessions created by a standalone server.
  • If a session is resumed, it keeps the limits it started with (agent steps, permissions), or isn't resumed when they can't be restored.
  • Resuming has an age limit and doesn't repeat on every restart.

Plugins

None

OpenCode version

Runs started on 2.0.18. Resumed by the service after it restarted on 2.0.26; seen again on 2.0.26.

Steps to reproduce

What happened:

  1. Oct 10: OPENCODE_CONFIG_CONTENT='{"agent":{"build":{"steps":25}},"permission":{"edit":"deny","bash":{"*":"deny","git log *":"allow","git show *":"allow","rg *":"allow","ls *":"allow"}}}' timeout -k 30 900 opencode run --standalone --agent build -m openrouter/z-ai/glm-5.3-flash -f brief.md "Follow the attached brief. Do not modify any files.", twice in parallel.
  2. Both runs ended after one step (the 499 above). Each session had one assistant message and no idle event.
  3. Oct 11: the background service restarted (around the auto-upgrade to 2.0.26). Both sessions got the restart notice and ran from ~11:25 to ~17:00 UTC: 861 + 799 steps, ~690M input tokens (mostly cache reads).

Minimal repro I expect to show the same thing:

  1. OPENCODE_CONFIG_CONTENT='{"agent":{"build":{"steps":3}},"permission":{"bash":{"*":"deny","ls *":"allow"}}}' opencode run --standalone --agent build -m <model> "<a task that needs many shell steps>"
  2. Kill the run process mid-step.
  3. opencode service restart
  4. The session gets the restart notice, runs past 3 steps, and runs shell commands other than ls.

Operating System

Linux (WSL2, kernel 6.18)

Terminal

N/A (headless opencode run, launched by a script)

Plugins

No response

OpenCode version

No response

Steps to reproduce

No response

Screenshot and/or share link

No response

Operating System

No response

Terminal

No response

Activity

  1. opencode-agent commented on Oct 13, 2026

    @opencode-agent
    Contributor

    Thanks for the detailed report. I reproduced this on 2.0.26, and it still happens on the latest dev (c5aabae).

    What I did, using isolated data dirs and a mock OpenAI-compatible model that keeps asking to run cat /etc/hostname > /tmp/opencode/pwned_<n>.txt:

    1. Started opencode run --standalone --agent build with OPENCODE_CONFIG_CONTENT setting agent.build.steps: 3 and an allowlist of "*": "deny" with only ls * allowed.
    2. Killed it with timeout while the model request was still pending.
    3. Ran opencode service start with the same data directory.

    The service added "The server restarted while you were working…" to the dead run's session and kept going: 9 steps, three times the limit of 3. All 9 cat commands ran, even though the allowlist denies them. The same command without the kill correctly gets Permission denied: shell and stops after 3 steps. So the limits are lost only when the service resumes the session.

    The script below reproduces it. Closely related: #51213 (live owners, addressed by #51215, which doesn't cover dead owners) and #51784 (recovery loses the client environment).

    Reproduction script (repro.sh)
    #!/usr/bin/env bash
    # Repro for #55076: a killed `opencode run --standalone` session is resumed by the
    # background service without the run's step limit / bash allowlist.
    # Usage: OPENCODE="opencode" bash repro.sh
    set -u
    OPENCODE=${OPENCODE:-opencode}
    W=/tmp/opencode/r55076; rm -rf $W /tmp/opencode/pwned_* /tmp/opencode/mock_release; mkdir -p $W/{cfg/opencode,data,state,cache,proj}
    export XDG_CONFIG_HOME=$W/cfg XDG_DATA_HOME=$W/data XDG_STATE_HOME=$W/state XDG_CACHE_HOME=$W/cache
    cat > $W/cfg/opencode/opencode.json <<J
    { "provider": { "mock": { "npm": "@ai-sdk/openai-compatible", "name": "Mock",
      "options": { "baseURL": "http://127.0.0.1:4999/v1", "apiKey": "x" }, "models": { "mock": { "name": "mock" } } } } }
    J
    cd "$(dirname "$0")"; bun mock.ts > $W/mock.log 2>&1 & MOCK=$!; sleep 1
    cd $W/proj && git init -q .
    # 1. standalone run with a 3-step limit and a bash allowlist; the model's first response hangs
    OPENCODE_CONFIG_CONTENT='{"agent":{"build":{"steps":3}},"permission":{"bash":{"*":"deny","ls *":"allow"},"shell":{"*":"deny","ls *":"allow"}}}' \
      timeout -k 5 25 $OPENCODE run --standalone --agent build -m mock/mock "list files" > $W/run.log 2>&1
    echo "run exited: $?"
    sleep 2; echo "leftover:"; ps -eo pid,args | grep -v grep | grep "serve" || true
    echo "--- mock log after run:"; cat $W/mock.log
    touch /tmp/opencode/mock_release  # from now on the mock answers
    # 2. start the background service (shares the data dir)
    $OPENCODE service start > $W/svc.log 2>&1; sleep 25
    echo "--- mock log after service start:"; cat $W/mock.log
    echo "--- files written by denied bash commands:"; ls /tmp/opencode/pwned_* 2>/dev/null
    $OPENCODE service stop >/dev/null 2>&1; kill $MOCK

    Run it: bash repro.sh

  2. samvallad33 commented on Oct 19, 2026

    @samvallad33

    Hi user753,

    Did you find what restarted them? I hit the same kind of problem where an agent kept running a command it had been allowed once. If that is what happened on your end, would you want to see something combining memory, authority, and security so an agent cant keep running past a limit nobody set?

    Sam

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