Skip to content

core: failed resumed turn leaves suspended claim until the next restart #56314

Closed as not planned

Description

@HeZ2z

Description

When restart continuity resumes a suspended Session, a transient provider/model failure inside the resumed turn can leave the Session suspended with its claim and pending background job row intact. The parent then remains silent indefinitely; the pending work is retried only after a later server restart.

This is distinct from the durable parent/child watcher problem in #36349 and from the plugin-prompt continuation report in #50751. The failure occurs after the host has already resumed the Session and the resumed turn itself fails.

Steps to reproduce

  1. Start OpenCode in managed service mode with persistent storage.
  2. Start a parent Session and an in-flight background child.
  3. Terminate the server while the parent/child are suspended.
  4. Before the next startup, configure the provider used by the resumed turn to fail deterministically (for example, point its base URL at a closed local port).
  5. Start the service and wait for restart continuity to resume the Session.
  6. Restore the provider without restarting OpenCode.
  7. Observe that the resumed turn has failed, the Session remains suspended, the pending background job remains, and the parent receives no completion.
  8. Restart the service again; the next sweep retries the work and it can complete.

Expected behavior

A failed resumed turn should release or re-queue its restart claim and expose a retryable terminal/recovery state, so a transient provider failure does not strand the Session until an unrelated future restart.

Actual behavior

The resumed turn dies after a partial assistant message; the suspension marker/claim and pending background job remain. No retry or user-visible recovery occurs during the running service.

Evidence

This was reproduced in an isolated OpenCode v2.0.30 managed-service instance while investigating oh-my-opencode-slim issue #1560. On the first restart, the resume attempt counter advanced to 1 and the pending state remained; a second restart advanced the counter and allowed recovery after the provider was restored.

The restart continuity code documents a retry budget for a crash inside the resumed turn, but the current behavior appears to reconcile it only on a subsequent sweep.

Related: #36349, #35646

OpenCode version

2.0.30 (isolated reproduction)

Plugins

Reproduced with the host path under test; plugin behavior was not required for the failure.

Operating System

macOS (isolated local instance)

Activity

  1. opencode-agent commented on Oct 17, 2026

    @opencode-agent
    Contributor

    Thanks for the detailed report. I couldn't reproduce this on 2.0.30.

    What I tried:

    1. Ran the managed background service with persistent storage and a mock provider that never answers, so a turn stays in flight.
    2. Killed the service with kill -9 while the Session was claimed.
    3. Pointed the provider's baseURL at a closed local port and ran opencode service start.

    Restart continuity resumed the Session (resume counter went to 1). While the resumed turn was retrying the provider, the Session stayed claimed, as expected for an active turn. After the capped retry schedule (about 84s of backoff) the turn failed with a provider.transport "ConnectionRefused" error. The claim was then released and the resume counter reset, with no second restart needed.

    I also tested on the latest dev (4a53028) a suspended parent with a running background subagent whose resumed turn fails with a transport error. The child's claim was released, the pending background job was cleared, and the parent got the subagent result with state="error". That test uses a faked runner, so it isn't a full end-to-end run.

    These would help us find the difference:

    • How long after the restart you checked. During the retry window (about 1.5 minutes) the Session is still legitimately claimed.
    • Whether the stranded Session was the parent or the background child, and how the child was started (built-in subagent tool or a plugin such as oh-my-opencode-slim).
    • The server log from the second start, around the resumed turn, and the last messages of the stranded Session (e.g. whether it ends with an idle entry with outcome failed).

    Related issues: #56313 (non-service startup skips restart continuity), #36349 (subagent parent completion across restart), #48657 (claim never released when snapshot capture stalls).

  2. HeZ2z commented on Oct 17, 2026

    @HeZ2z
    Author

    Retested properly and I have to retract this — you were right.

    Same harness, but this time I did not restart again and sampled for 4.8 minutes:

    T+ time_suspended resume_attempts idle_outcome kv job.background/* parent synthetic
    48s both still suspended 1 — 1 restart notice only
    108s cleared 0 failed 0 source:"subagent", state:"error"
    288s cleared 0 failed 0 unchanged

    So the claim is released and the counter resets after the capped retry schedule, the pending background job is cleared, and the parent does receive state="error" — no second restart needed, exactly as you described.

    My earlier evidence was taken at T+40s, inside the ~84s backoff window, which is indistinguishable from a stranded session; I then restarted before the window closed, so I never saw the release. The only other contributing factor: nothing about the retry is visible at the default log level (no transport/refused line at INFO).

    Closing as not planned — it was my report. For future triage, the discriminator I should have used: time_suspended + resume_attempts >= 1 + a job.background/ row is the normal retry window; it ends with idle/outcome:"failed" and a state="error" completion for the parent. Thanks for the precise reproduction notes — they're what let me find my own measurement error.

  3. opencode-agent commented on Oct 17, 2026

    @opencode-agent
    Contributor

    Thanks for retesting and posting the timeline. That matches what I saw: during the capped retry window (about 84s of backoff), the Session is still active and keeps its claim. Once the window ends, the turn finishes with an idle entry whose outcome is failed. The claim is released, the resume counter resets, and the parent gets the subagent result with state="error". Nothing else is needed for this report.

    You also noted that the retries don't appear in the log at the default INFO level, which makes a normal retry window look like a stuck Session. If you'd like that changed, please open a separate issue for it.

  4. opencode-agent commented on Oct 20, 2026

    @opencode-agent
    Contributor

    I'm closing this as not planned, since you retested and confirmed it isn't a bug. A failed resumed turn releases its claim once the capped retry window ends (about 84s of backoff). The pending background job is then cleared and the parent gets the state="error" result, with no second restart needed. Thanks again for following up with the timeline. If the retries not showing in the log at the default level is worth changing, please open a separate issue for it.

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