Repository navigation
[Bug]: Sidebar hides a finished thread's unseen completion while a background shell it left running is still alive #14872
Description
Activity
- addedbugSomething is broken or behaving incorrectly.Something is broken or behaving incorrectly.needs-triageIssue needs maintainer review and initial categorization.Issue needs maintainer review and initial categorization.
on Oct 2, 2026 Drafted with Claude (Opus 5.5) in Claude Code at the reporter's request.
Before submitting
- I searched existing issues and did not find a duplicate. Related: [Bug]: A thread whose agent leaves a dev server running stays "Monitoring" on desktop with no completion alert, while mobile reports it completed #13965 reported this together with the missing alert. Its fix, fix(clients): a thread left running a dev server alerts as completed #14213, changed only the alert: "The sidebar and the mobile list still show Waiting while a dev server runs." This issue is about that sidebar half. Sidebar can miss unread Completed status for newly finished threads #3131 is a separate cause of the same symptom (a thread with no recorded visit never reads as unread). [Bug]: Completion sound fires when subagents or monitors finish, even though the agent resumes working #13625 is the opposite case (subagents and monitors that wake the agent), which this request should not change.
Note
Grok responding on behalf of Julius.
Triage
Thanks for the clear write-up, @vanderlindenma! I confirmed this on
mainat cc1e634. The turn already counts as finished for alerts, but the sidebar and the mobile thread list still hide that.A background shell that outlives the turn stays on the pending roster as
kind: "command". Whenever that roster isn't empty,presentThreadShellkeeps runtime atidle:t3code/packages/client-runtime/src/state/models.ts
Lines 166 to 171 in cc1e634
// latestRun keeps the latest run's status for history presentation. // A failed latest run outranks the roster, so the failure stays visible. function shellRuntime(thread: OrchestrationV2ThreadShell): ThreadRuntimeSummary | null { if (thread.latestRunId === null && thread.activeProviderThreadId === null) return null; const parkAtIdle = (thread.pendingBackgroundTasks?.length ?? 0) > 0 && thread.status !== "failed"; const status = parkAtIdle ? "idle" : (thread.activityRunStatus ?? thread.status); The sidebar maps
idletowaitingbefore it checks for an unseen completion, and a waiting row is dimmed even when its completion is unread:t3code/apps/web/src/components/Sidebar.logic.ts
Lines 947 to 961 in cc1e634
export function resolveSidebarThreadStatus(thread: SidebarThreadStatusInput): SidebarThreadStatus { if (thread.hasPendingApprovals) { return "approval"; } if (thread.hasPendingUserInput) { return "input"; } if ( thread.runtime !== null && ["preparing", "queued", "starting", "running", "waiting"].includes(thread.runtime.status) ) { return "working"; } if (thread.runtime?.status === "idle") { return "waiting";
t3code/apps/web/src/components/Sidebar.logic.ts
Lines 927 to 936 in cc1e634
export function shouldRecedeSidebarThread(input: { status: SidebarThreadStatus; isUnread: boolean; isWoke: boolean; isActive: boolean; isSelected: boolean; }): boolean { if (input.isActive || input.isSelected || input.status === "input") return false; if (input.status === "working" || input.status === "waiting") return true; if (input.status === "ready" || input.status === "approval") {
t3code/apps/web/src/components/Sidebar.tsx
Lines 1253 to 1278 in cc1e634
const shouldRecede = shouldRecedeSidebarThread({ status, isUnread, isWoke, isActive: props.isActive, isSelected, }); // Status hues follow the system-wide convention set by sidebar v1 and the // mobile Live Activity/widgets (amber approval, indigo input, sky working) // so a thread reads the same color everywhere it surfaces. const topStatus = status === "working" ? { label: "Working", icon: "working" as const, // No shimmer: a label that animates forever is noise in a sidebar // full of them (and repaints every vsync on high-refresh displays). className: "text-info", } : status === "waiting" ? { // Waiting is calm background presence (post-settle background // roster), not active progress, so the label keeps full strength. label: "Waiting", icon: null, className: "text-muted-foreground", The row label works the same way:
waitingwins, and the unreadDonelabel only shows once status is ready. When the shell finally exits, the roster clears, the row leaveswaiting, and the unread style appears late, which matches what you saw. On 0.0.40 these were labeledMonitoringandCompleted; on current main they'reWaitingandDone.Alerts already use a different rule.
backgroundWorkHoldsCompletiondoesn't hold completion forcommand, so the toast, sound, and mobile push fire when the turn ends. Subagents, monitors, and unnamedbackground_taskwork still hold it:t3code/packages/shared/src/orchestrationV2PendingBackgroundWork.ts
Lines 64 to 84 in cc1e634
* completion alert (desktop/web notification and the mobile push). Commands, * such as dev servers and other long-lived shells, do not: the agent is done * and may leave them running for hours. Subagents and monitors do, because * they wake the agent and it continues (#13625). Work the adapter cannot name, * including kinds this build does not know, holds as the conservative choice. */ export function backgroundWorkHoldsCompletion( tasks: ReadonlyArray<Pick<PendingBackgroundWorkTask, "kind">>, ): boolean { return tasks.some((task) => backgroundWorkKindHoldsCompletion(task.kind)); } function backgroundWorkKindHoldsCompletion(kind: PendingBackgroundWorkTask["kind"]): boolean { switch (kind) { case "command": return false; case "subagent": case "monitor": case "background_task": return true; }
t3code/apps/web/src/components/ThreadNotificationCoordinator.tsx
Lines 129 to 132 in cc1e634
// Waiting only on commands (a dev server) is done; subagents and monitors wake the agent. const settled = status === "ready" || (status === "waiting" && !backgroundWorkHoldsCompletion(thread.pendingBackgroundTasks)); The mobile list has the same gap:
resolveThreadListV2Statusmapsidletowaitingwithout looking at the task kind.Related: this is the sidebar half of closed #13965. The merged #14213 only fixed the alert, and its description said so. It's different from open #3131, where a thread with no visit watermark never counts as unread. Here you opened the thread and left it, so the watermark exists and
waitinghides the completion anyway. It's also not open #13625: subagents and monitors should keep holding completion, because they wake the agent back up.Waitingexists so a thread doesn't flash as done while background work is about to resume (that was the idea behind the closed PR #4415). That still makes sense for subagents and monitors, but not for a command the agent already finished with and left running. A fix could use the same kind split for the sidebar status, the dimmed-row treatment, the unread label, and the mobile list: pending work that's only commands would show the unseen completion, and anything that holds completion staysWaiting. A separate "still running" hint would be nice but isn't needed to fix the missed completion.- addedvia-triageFiled through npx t3 triageFiled through npx t3 triageand removedneeds-triageIssue needs maintainer review and initial categorization.Issue needs maintainer review and initial categorization.
on Oct 2, 2026
Before submitting
Area
Not sure
Steps to reproduce
local_bash, e.g. a dev server or a long command it no longer needs), then end its turn with a final answer.Expected behavior
The thread reads as finished and unread, like any other thread whose turn ended while I was elsewhere, ideally with a quiet hint that a process from it is still running. With several agents running, the bright/unread row is how I find threads that need me.
Actual behavior
The row shows "Waiting" ("Monitoring" in 0.0.40), dimmed, for as long as the shell lives, which can be hours. The unseen "Completed" pill and the unread row style never show. When the shell finally exits, the thread lights up as unread long after it actually stopped.
In practice most of my "done" threads end up looking just like threads that are still running, so I can't tell from the sidebar which ones need me.
Impact
Major degradation or frequent failure
Version or commit
main @ cc1e634
Environment
macOS 26.7
Logs or stack traces
Screenshots, recordings, or supporting files
No response
Workaround
No response