pingdotgg / pingdotgg/t3code

[Bug]: Stuck OpenCode deletion blocks new chats in ThreadDeletionReactor.drainThrough

Open
#11,889 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

accepted bug via-triage
Dominant language
TypeScript
Stars
23k
Forks
5.9k
Avg merge
11h 14m
Merged PRs (30d)
357

Description

Before submitting
  • I searched existing issues and did not find a duplicate.
  • I included enough detail to investigate the problem.
Area

apps/server

Summary

Deleting an OpenCode thread left processThreadDeleted stuck in cancelPendingOpenCodePrompt for 242,562,870 ms, about 67.4 hours. New thread creation then blocked in ThreadDeletionReactor.drainThrough, although the remote environment remained connected and HTTP requests succeeded.

Steps to reproduce

Observed sequence:

  1. Run a headless T3 0.0.40 server on Linux with the OpenCode provider, connected to the macOS desktop client.
  2. Delete an OpenCode thread while its prompt admission/cancellation state is unsettled.
  3. Create a new chat and send its first message.
  4. The thread.created event is persisted, but the request waits in ThreadDeletionReactor.drainThrough and never reaches thread.turn-start-requested.

The exact timing that caused the original OpenCode cancellation to remain unsettled has not been reproduced deterministically. The blocked new-thread path was reproduced twice against the affected live process with the following harmless probe:

  • Read an existing accepted thread.created command ID, payload, and receipt sequence from SQLite in read-only mode.
  • Replay it as a thread.create command over authenticated orchestration.dispatchCommand.
  • T3 deduplicates the existing command ID, then still executes the thread-creation drain barrier.
  • Assert that the RPC acknowledges the existing receipt within eight seconds.

This probe creates no additional thread and submits no model prompt. It timed out before recovery and passed in 18 ms after restarting the primary server.

Expected behavior

Provider teardown should finish or fail within a bounded period. A stuck deletion should not prevent unrelated new chats from starting.

Actual behavior

The client remains connected. New chat requests persist the empty thread, then wait indefinitely behind the shared deletion worker. Two original dispatch spans lasted 121,844.8 ms and 47,730.2 ms before client interruption. On service shutdown, the previously open deletion span finally appeared with a duration of 242,562,870 ms.

Impact

Blocks new chat creation on the affected server until recovery.

Version or commit

T3 server and desktop 0.0.40. The unbounded cancellation wait and shared drain are also present in the upstream main files inspected on September 15, 2026.

Environment

Linux x86_64 headless server, Node v22.23.2, macOS T3 desktop 0.0.40, OpenCode provider. Private paths, thread IDs, and chat content omitted.

Logs or stack traces

Sanitized summary of the shutdown trace:

span: processThreadDeleted
durationMs: 242562870.251242
exit: Interrupted during server shutdown

cancelPendingOpenCodePrompt
  <- stopOpenCodeContext
  <- OpenCodeAdapter.stopSession
  <- ProviderService.stopSession
  <- processThreadDeleted

new-chat request:
ws.rpc.orchestration.dispatchCommand
  -> ThreadDeletionReactor.drainThrough

Relevant code:

  • OpenCodeAdapter.ts: cancelPendingOpenCodePrompt awaits Fiber.interrupt(admission.promptFiber) and Deferred.await(admission.submissionSettled) without a timeout, before the bounded remote-abort operations.
  • ThreadDeletionReactor.ts: drainThrough awaits the shared worker.drain after the event watermark.
  • ws.ts: explicit thread creation and first-message bootstrap both await this drain.

The traces identify the cancellation subtree but do not distinguish whether the stuck await was fiber interruption or submissionSettled.

Workaround

Restarting the headless T3 service after confirming no active AI turns cleared the stall. The same accepted-command replay then completed in 18 ms. HTTP-only health checks missed the failure.

There was also a second server started by npx t3 status during troubleshooting. It started after the first observed timeout; stopping that duplicate alone did not clear the stall. Restarting the primary process did.

Suggested regression coverage

Hold an OpenCode prompt cancellation open, delete its thread, then create/start an unrelated thread. Verify teardown terminates or reports a bounded failure and unrelated thread creation still completes. Include cancellation before prompt-fiber assignment and during submission, so completion is guaranteed across both paths.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with cancelPendingOpenCodePrompt in apps/server/src/provider/Layers/OpenCodeAdapter.ts, then trace the shared drain in apps/server/src/orchestration/Layers/ThreadDeletionReactor.ts and the creation paths in apps/server/src/ws.ts. Add regression coverage for cancellation before prompt-fiber assignment and during submission, verifying teardown is bounded and unrelated thread creation completes.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
backend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.