Repository navigation
Description
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
- Start a top-level thread on the Claude provider. Ask it to run
sleep 900with the Bash tool andrun_in_background: true, callt3_background_waiton that background task, then end its turn. (t3_background_waitdoes not keep the turn open.) - When
t3_thread_readshows the threadcompletedwithactiveRunId: null, callt3_thread_interrupton it from another thread: once with norunId, then once with the finished run'srunId. - Check the process list. The
sleep 900is still running.
Expected behavior
An agent can end a top-level thread's background work after its turn has ended. For example, t3_thread_interrupt on a top-level thread with no active run could send thread.stop, the way task_cancel does for a delegated child.
Actual behavior
t3_thread_interruptwith norunIdreturns{"runId":null,"status":"no_active_run"}.t3_thread_interruptwith the finished run'srunIdreturns{"status":"completed"}.- In both cases the background command keeps running. Ending its process by hand stopped it. I did not try the web or desktop Stop button for background work ([Bug]: Mobile has no way to stop background work after the turn settles #14655). From reading the installed server code, no other orchestration MCP tool and no
t3CLI subcommand sendsthread.stopto a top-level thread, so an agent cannot reach that stop.
This matches the tool description ("No effect when idle"), but it leaves a top-level thread with no agent-callable stop for background work that outlives its turn.
For a delegated child, task_cancel does end the command when the child called t3_background_wait on it (a Monitor works too), so task_status reads waiting_for_children. It sends thread.stop, which also stops the thread's background work, and it accepts a task in waiting_for_children after its run has finished. In one run where the child skipped the wait, task_cancel returned cancel_requested after the run had ended, the task then read completed, and the command kept running. A top-level thread has no equivalent.
Related, not duplicates: #16739 (delegated-task cancel; a comment there sees no_active_run on a turn-ended child), #14655 (web/desktop Stop for background work after a turn), #19926 (closed; asked for background work on t3_thread_read; on this build t3_thread_read still shows a delegated child completed with activeRunId: null while task_status reads waiting_for_children).
Impact
Minor bug or occasional failure
Version or commit
t3 v0.1.3-nightly.20261025.3669
Environment
macOS 26.5 (top-level case) and macOS 27.0 (delegated-child cases). Headless t3 serve runs as a launchd service on each. Threads use the Claude provider, Claude Sonnet 5.5, Claude Code 2.1.311. One agent thread manages the others through the orchestration MCP tools; in the top-level case it reached the thread's server from another machine.
Workaround
Before stopping or archiving a top-level Claude thread, have the thread report each background command's pid, start time and task output file. End only a pid that lsof -t <output file> lists and whose start time matches, then check that it is gone.
Activity
juliusmarminge commentedon Oct 27, 2026
This is fixed on main by cb98850 (https://git.cafe/pingdotgg/t3code/pulls/16684, merged 2026-10-27), which came after the 2026-10-25 nightly you tested.
t3_thread_interruptnow takesstop: true. That sendsthread.stopto the thread, the same thing its Stop button does, and it also works when the thread has no active run but still has background work.What I ran on main at b88da91, with a dev server on a fresh database, the Claude provider and Claude Sonnet 5.5:
sleep 917through Bash withrun_in_background: true, calledt3_background_wait, and ended its turn.t3 thread showreportedcompletedwithactiveRunId: null, andsleep 917was still running.t3_thread_interruptwith onlythreadIdreturned{"runId":null,"status":"no_active_run"}. With the finishedrunIdit returned{"status":"completed"}. The sleep was still running after both calls, as you reported.t3_thread_interruptwiththreadIdandstop: truereturned{"runId":"…:ordinal:1","status":"interrupt_requested"}, andsleep 917was gone.sleep 918in the background and did not callt3_background_wait.t3_thread_interruptwithstop: truereturnedinterrupt_requested, andsleep 918was gone.A plain interrupt still does nothing on an idle thread, as the tool description says. Pass
stop: trueto end a top-level thread's background work after its turn. It ships in the next nightly.