[Bug]: Stop does not lift the background-work guard: provider/model switch stays refused ("press Stop" is a dead end)
Maintainer antworten meist innerhalb von 1 Tag
Dieses Issue hat noch niemand übernommen.
Bewertung
- Schwierigkeit
- 4/5
- Geschätzter Aufwand
- 3-5 Tage
- Anfängerfreundlichkeit
- 48/100
- Issue-Typ
- Bug
- Klarheit
- Klar beschrieben
- Aktivitätsstatus
- Aktiv
- Tech-Stack
- electron, sqlite, typescript
Rechercherichtung
Start in apps/server, where the issue identifies sessionHasBackgroundWork, startNextQueuedRun, the direct-send sessionsToDetach path, and the Claude query-replace guard; trace how thread.stop and run.interrupt update session state. Compare the adapter's in-memory background-work probes with derivePendingBackgroundWork, then reproduce a refused provider or model switch after Stop. Done means an explicit Stop lets the switch proceed despite stale adapter state, with the failure path diagnosable.
Vom Indexierungsmodell aus dem Issue-Text verfasst.
Beschreibung
Before submitting
- I searched existing issues and found related ones (#32296, #16073, #25715) but not this one: here Stop itself never lifts the refusal, so the message that says "press Stop" is a dead end.
- I included enough detail to investigate the problem.
Area
apps/server
Environment
T3 Code (Beta) 0.1.9 desktop (Electron 44), Linux x86_64. Claude Agent and Pi providers, switched back and forth inside one long thread.
What happens
After a thread has used background work (background shell commands, an Agent subagent, long run_in_background commands), sending a message that changes provider or model is refused with:
The previous provider is still running background agents or commands, and switching would end them. Wait for them to finish, or press Stop, then send the message again.
(or, for a model/setting change inside a Claude session: Claude is still running background agents or commands, and this model or setting change would end them...)
Everything the agent started has long finished, nothing is running, and pressing Stop (button, thread.stop, or run.interrupt) changes nothing: the same refusal comes back on every retry. It only went away when the stuck provider session row was set to stopped directly in statev2.sqlite (the guard skips sessions whose status is stopped/error), which is how I got unstuck the first time. A server restart presumably clears it too, since the flag lives in memory (not tested).
I could not capture which in-memory condition stays set: the refusal is a dispatch error and is not written to the logs or to orchestration_v2_command_receipts, so it is invisible afterwards. That is worth fixing on its own.
Why Stop cannot help (from reading the 0.1.9 server bundle, apps/server/dist/runtimeLayer-*.mjs)
Two independent sources of truth:
- The refusal comes from
sessionHasBackgroundWork(session, providerThreads), called fromstartNextQueuedRun(which then holds the queue) and from the direct-send path (sessionsToDetach, which refuses). It asks the adapter's in-memory state:runtime.hasPendingBackgroundWork.- Claude: any non-empty entry in
pendingBackgroundTasksByNativeThread, anysessionSubagentsByTaskIdentry stillrunning, or any buffered user/assistant/result frame inwakeBuffers. - The non-Claude adapter whose probe is
owesWork(I did not confirm which driver exactly; it was the one used by my second provider): running shells, image jobs,wakes.length > 0,reports.size > 0, a background call, or a busy subagent. - Any failure of the probe is treated as busy:
probe.pipe(catchCause(() => succeed(true))).
- Claude: any non-empty entry in
- Stop (
dispatchThreadStop/dispatchRunInterrupt) only reconciles the projection: it derives pending work withderivePendingBackgroundWorkover turn items and runs and marks those settled. It never touches the adapter's roster, wake buffers, reports, or wakes.
So when the adapter flag is stale (or the probe throws) while the projection is already settled, Stop has nothing to settle and returns, and the guard keeps refusing. The error text tells the user to press Stop, which cannot work in this state.
Expected behavior
An explicit Stop should be authoritative for the thread: after it, a provider or model switch proceeds and the old session is detached (which ends whatever is really still running, as the message warns). Stale in-memory state must not survive an explicit Stop.
Suggested fix
Smallest change I tested: remember the explicit Stop per thread and let it satisfy the guard for a short window. Local patch against 0.1.9 (module-level helper plus one check at each of the three guards):
const stopAck = new Map();
const STOP_ACK_TTL_MS = 10 * 60 * 1000;
function markStopped(threadId) { stopAck.set(threadId, Date.now()); }
function stopRecent(threadId) {
const at = stopAck.get(threadId);
if (at === undefined) return false;
if (Date.now() - at > STOP_ACK_TTL_MS) { stopAck.delete(threadId); return false; }
return true;
}
// in the command switch, only for the user-facing commands (not internal callers of dispatchRunInterrupt):
case "run.interrupt": markStopped(command.threadId); /* ... */
case "thread.stop": markStopped(command.threadId); /* ... */
// guards:
// startNextQueuedRun: if (!stopRecent(threadId) && (yield* sessionHasBackgroundWork(session, ...))) busySessionIds.add(session.id)
// direct send: if (!stopRecent(command.threadId) && (yield* sessionHasBackgroundWork(detaching, ...))) return yield* new OrchestratorDispatchError(...)
// Claude query replace: ... && !existing.stopping && !stopRecent(turnInput.threadId) && (yield* liveProcessRunsBackgroundWork(existing))
A more thorough fix would have Stop clear the adapter state too (roster, wake buffers, pi reports/wakes), and the probe should not fail closed: a throwing hasPendingBackgroundWork should be logged and not treated as "busy forever". Also please persist or log the dispatch error so a stuck guard can be diagnosed after the fact.
Not verified
- I could not reproduce deterministically. It showed up twice in one long thread with heavy use of background commands and several provider switches (Claude Agent <-> Pi).
- The patch is installed locally; I have not yet seen a second occurrence with it, so I can only say it should remove the dead end, not that it fixes the leak that leaves the flag set.
Related: #32296 (same guard refuses a direct send that the queued path accepts), #16073, #25715.
- Vorherrschende Sprache
- TypeScript
- Sterne
- 24.8k
- Forks
- 6.4k
- Ø Merge
- 4 Std. 16 Min.
- Gemergte PRs (30 T.)
- 352
Entwicklungsumgebung
Startet den Dev-Container des Projekts im Browser, mit Ihrem eigenen GitHub-Konto.
- Kein Dockerfile und keine Docker-Compose-Datei
- Hat eine Pull-Request-Vorlage
- Beitragsleitfaden lesen
Erste Schritte
- Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
- Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
- Forken Sie das Repository und arbeiten Sie in einem Branch.
- Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.
Mehr aus pingdotgg/t3code
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 84/100
Maintainer antworten meist innerhalb von 1 Tag
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 78/100
Maintainer antworten meist innerhalb von 1 Tag
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 78/100
Maintainer antworten meist innerhalb von 1 Tag
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 67/100
Maintainer antworten meist innerhalb von 1 Tag
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 82/100
Maintainer antworten meist innerhalb von 1 Tag
Alle Issues in pingdotgg/t3code
Ähnliche Issues
-
idea-scout idea:453
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 83/100
jeanibarz/agent-notebook#453 ·
Maintainer antworten meist innerhalb von 1 Tag
-
level/task
Schwierigkeit 1/5 Unter einer Stunde Anfängerfreundlichkeit 95/100
wazuh/wazuh-dashboard-plugins#9642 ·
Maintainer antworten meist innerhalb von 1 Tag
-
bug
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 91/100
unicef/adt-studio#1112 ·
Maintainer antworten meist innerhalb von 2 Tagen
-
feature request good first issue
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 88/100
TabularisDB/tabularis#1513 ·
Maintainer antworten meist innerhalb von 2 Tagen
-
Schwierigkeit 1/5 Unter einer Stunde Anfängerfreundlichkeit 75/100