Repository navigation
Description
Area
apps/server
Steps to reproduce
Self-hosted T3 server (Linux, systemd user service), web client, Claude provider.
- Control: in a new Claude thread, ask the agent to run
sleep 30withrun_in_backgroundand end its turn. It is woken when the command exits. This works. - In the same thread, ask the agent to run
sleep 180withrun_in_background, callt3_background_waiton that task, and end its turn. - After the turn ends, send SIGTERM to that thread's
claudeprocess only. Leave the T3 server running. The backgroundsleepdies with the process. - Wait past the 180 seconds.
- Send any message to the thread.
Expected behavior
When the provider process exits while the thread is waiting on background work, the agent is woken with a short note saying which background tasks were lost, as the server-restart path does ("T3 Code restarted and stopped background work"). It can then re-arm them.
Actual behavior
No wake and no note. At the moment of the kill T3 records the provider thread as idle with an empty pending-task list, and the session still shows ready. Nothing happens until the next user message (step 5). That message starts a new Claude process, which then reports: "Background shell command didn't finish before the previous session ended".
In normal use this looks like an agent that says "I'll report back when CI finishes" and never does. We've hit it several times with agents waiting on CI, a merge or a long test. The agent only noticed the lost watcher after the user poked the thread. In those real cases we can't tell whether the server restarted or only the provider process died. The repro above isolates the provider-death path.
From reading the source (commit 30654b3), the cause looks like this: when a Claude query process exits and no turn is active, finalizeActiveTurnAfterQueryExit in ClaudeAdapterV2.ts calls resetBackgroundTaskStateForNativeThreadProcess. That clears the pending background tasks and emits an idle provider thread. Nothing records the awaited tasks as cancelled work, and nothing queues a continuation. The startup recovery that handles server restarts never runs here, because the server didn't restart.
Impact
Major degradation or frequent failure
Version or commit
reproduced on 0.1.7-nightly.20261102.4066. Current install is 0.1.7-nightly.20261103.4119 (from t3 --version), not re-tested yet.
Environment
Ubuntu Linux (kernel 6.8), Node 24.19, Claude Code 2.1.317 via the Claude Agent SDK, model claude-sonnet-5-5. Self-hosted server, web client. Codex not tested.
Logs or stack traces
# Timeline from the repro (UTC)
13:35:15 control: background `sleep 30` wakes the agent with no user message
13:35:36 test: `sleep 180` started in background, t3_background_wait called, turn ends
13:35:50 SIGTERM to the thread's claude process; server PID and uptime unchanged
provider thread -> status idle, pending background tasks []
13:40:40 no wake, no note (2m10s after the job would have finished)
13:40:59 user message -> new claude process -> task_notification:
"Background shell command didn't finish before the previous session ended"Workaround
Send any message to the thread after a provider process dies. For work that must finish, have the agent use a bounded foreground wait instead of a background one.
Suggested fix
- In the query-exit path with no active turn, check whether the roster being cleared held awaited tasks before clearing it. If it did, record them as cancelled background work on the thread's last run and queue the same continuation that server-restart recovery uses. The agent then wakes with "N background tasks were lost when the provider process exited: …" and can re-arm them.
- Longer term, persist registered background waits outside the provider process so a restarted process, or the server, can re-check or re-arm them.
Related
#28208, the same symptom after a server restart (fixes in #28994 and #28500). This report is the provider-process-exit path with the server still up, which that recovery doesn't cover. Also #29319 (subagents killed when the Claude process ends) and #29772 (t3_background_wait succeeding on missing task IDs).
Activity
lojzezust commentedon Nov 4, 2026
I'm having similar problems of background processes/watchers randomly disappearing after some time. My workflow requires the use of long-term watchers that wake claude in case of certain events (e.g. training job failures), and this background task flakiness is making T3 a bit unusable in the current state.
royalaid commentedon Nov 7, 2026
#30214 matches what we saw for a process that exits on its own. Two adjacent paths we hit, in case they belong in the same fix or a follow-up:
ProviderSessionManagerreleases a Claude session foridle_timeout(including the 4 hour pin expiry) and onruntime_error, andwriteReleasedBackgroundWorkEventsthen removes the dead tasks from the roster and writes nothing else. A thread whose last run had finished gets no run, no note and no error; it just stops being Waiting. fix(server): keep Claude background work alive past idle pin limit #29354 removes the pin-expiry case for process-bound work. As far as we can tell, fix(server): wake the agent when Claude exits while it awaits background work #30214 treats these as deliberate stops and reports nothing. Our test "idle release of a session past its pin delivers one notice" fails on main.We found path 1 by reading the code and confirmed it with failing tests; we did not capture a live occurrence.