Skip to content

[Bug]: Resuming an OpenCode subagent (Task task_id) reuses provider-turn ordinal → UNIQUE violation fails parent turn while opencode keeps running #19883

Open

Description

@lageorgem

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

apps/server

Steps to reproduce

  1. On the OpenCode provider, have the lead agent dispatch a subagent with opencode's Task tool and let it complete. T3 projects the child's turn as provider turn ordinal 1 on the child provider thread.
  2. In a later step of the same parent turn, have the lead resume that subagent by passing its task_id. This sends a second prompt into the same child native session.
  3. Wait for the resumed child turn to finish.

Expected behavior

The child's second turn is recorded as ordinal 2 on the same provider thread, and the parent turn continues.

Actual behavior

This reproduced twice: on the original thread, and on a fresh fork of it within its first turn.

Parent thread Child native session Child's existing turn Collision
4c5716b5…, run 14 ses_ec0a70275ffemMJ0KJEwxTU6UM ordinal 1, completed 11:35:59–11:36:13Z 11:36:34Z
f4bc3d47… (fork), run 1 ses_ec09b890bffes49xaBlE4s3vnY ordinal 1, completed 11:48:31–11:48:55Z 11:49:09Z

Suggested fixes:

Impact

Blocks work completely

Version or commit

0.0.46-nightly.20261015.3145

Environment

macOS 26.6.2 (arm64), T3 Code (Nightly) desktop app, opencode 1.19.0, anthropic/claude-opus-5-5 via opencode, full-access runtime

Logs or stack traces

2026-10-15T11:49:09Z orchestrationV2.EventSink.write
  orchestration_v2.thread_id=thread:provider:opencode:native-thread:ses_ec09b890bffes49xaBlE4s3vnY
ProjectionStoreApplyEventError: Failed to apply orchestration projection event provider-turn.updated.
  [cause]: SqlError/UniqueViolation: UNIQUE constraint failed:
    orchestration_v2_projection_provider_turns.provider_thread_id, orchestration_v2_projection_provider_turns.ordinal
# (identical at 11:36:34Z for ses_ec0a70275ffemMJ0KJEwxTU6UM)

# Parent thread f4bc3d47 after the collision:
run 1: failed "Provider turn failed."; parent provider turn status=running, completedAt=null
run 2 (11:50:56Z), run 3 (11:55:50Z): failed in ~200 ms, ProviderAdapterTurnStartError
opencode GET /session/status at ~12:00Z:
  ses_ec09bf7a3ffejuKpInFs6JfJxi (parent)        busy
  ses_ec094e0bc… / ses_ec094a727… (new subagents) busy
worktree gained 6 commits 14:53-15:04 local while T3 showed the thread as failed

Workaround

  • Don't resume opencode subagents by task_id. Dispatch a fresh subagent instead.
  • If it already happened, abort the native session. Call POST /session/<parent ses_id>/abort on the T3-managed opencode server; the port is in ~/.t3/userdata/opencode-servers/*.json. Then send a new message.

Related: #19875 (the downstream budget wedge, fixed by #19414), #15542, #15447.

Activity

  1. juliusmarminge commented on Oct 15, 2026

    @juliusmarminge
    Member

    Note

    Grok responding on behalf of Julius.

    This is a confirmed bug on main in the OpenCode adapter's subagent tracking: nothing in that code handles a resumed child session.

    In packages/provider-opencode/src/server/adapter.ts, subagent context is keyed by the Task tool part's id (subagentsByNativeItemId). When a later Task call targets an existing child session, the adapter builds a new context for the same child session and calls threads.set(childSessionId, …) with a fresh state where nextChildTurnOrdinal: 1. The child provider-thread id comes from the native session id, so it is the same thread as before. The resumed child turn then gets ordinal: state.nextChildTurnOrdinal++, which is 1 again, and hits the unique index orchestration_v2_projection_provider_turns(provider_thread_id, ordinal) from migration 055_OrchestrationV2.ts.

    This isn't a duplicate. #15542 (Claude, transient DB lock) and #15447 (Cursor) cover the same downstream gap: a failed run leaves the native process running unsupervised. This issue is a separate trigger specific to OpenCode, and the orphaned-run part belongs with those issues.

    Workaround for now: have the lead dispatch a fresh subagent instead of resuming one by task_id.

  2. added
    acceptedfeature request accepted
    bugSomething is broken or behaving incorrectly.
    via-triageFiled through npx t3 triage
    on Oct 15, 2026
  3. linkendr commented on Oct 17, 2026

    @linkendr

    Another reproduction, on the stable release 0.1.0 (desktop + headless service, macOS arm64, opencode 1.18.30, openai/gpt-6-astra via opencode). PR #19888 is not in 0.1.0: the bundled adapter.ts still builds the child state with an unconditional nextChildTurnOrdinal: 1.

    Step Time (UTC) Evidence
    Child session ses_eb6899e2dffekU65BFTJvXsFvJ runs its first Task turn 10:44:17 to 10:53:01 stored as provider turn ordinal 1 (native-turn:msg_1497661d5001…)
    Lead resumes it with task_id 10:55:40.962 UNIQUE constraint failed: orchestration_v2_projection_provider_turns.provider_thread_id, orchestration_v2_projection_provider_turns.ordinal on thread:provider:opencode:native-thread:ses_eb6899e2…
    T3 fails the parent run 10:55:41.109 "Provider turn failed."; the parent provider turn stays running
    opencode keeps going 10:55:41 to 11:07 resumed child completes at 10:58:01; the lead dispatches another subagent at 11:00:33
    Next user message 11:02:07 fails in 0.13 s with ProviderAdapterTurnStartError; no request reaches opencode, consistent with the already has an active turn guard in startTurn

    Recovery was the same as in the report: POST /session/:id/abort on the opencode server.

    Two notes on #19888:

    • It reuses the in-memory child state. If that state is gone (for example after a server restart while the parent session is resumed), a resumed child would start at ordinal 1 again. Allocating from the stored max(ordinal) + 1 for the provider thread would cover that case too.
    • The orphaned-run part still applies: after the projection write fails, T3 does not abort the native session, and the adapter's activeTurn appears to stay set, so every new message is refused until the native session is aborted by hand.
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

    acceptedfeature request acceptedbugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions