[Bug]: Stuck OpenCode deletion blocks new chats in ThreadDeletionReactor.drainThrough
Nobody has claimed this yet.
- 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:
- Run a headless T3 0.0.40 server on Linux with the OpenCode provider, connected to the macOS desktop client.
- Delete an OpenCode thread while its prompt admission/cancellation state is unsettled.
- Create a new chat and send its first message.
- The
thread.createdevent is persisted, but the request waits inThreadDeletionReactor.drainThroughand never reachesthread.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.createdcommand ID, payload, and receipt sequence from SQLite in read-only mode. - Replay it as a
thread.createcommand over authenticatedorchestration.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:
cancelPendingOpenCodePromptawaitsFiber.interrupt(admission.promptFiber)andDeferred.await(admission.submissionSettled)without a timeout, before the bounded remote-abort operations. - ThreadDeletionReactor.ts:
drainThroughawaits the sharedworker.drainafter 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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