Skip to content

[Bug]: Sidebar hides a finished thread's unseen completion while a background shell it left running is still alive #14872

Description

@vanderlindenma

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

Not sure

Steps to reproduce

  1. In a Claude thread, have the agent start something as a background shell (local_bash, e.g. a dev server or a long command it no longer needs), then end its turn with a final answer.
  2. Switch to another thread before the turn ends.
  3. Look at the first thread in the sidebar.

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

Activity

  1. added
    bugSomething is broken or behaving incorrectly.
    needs-triageIssue needs maintainer review and initial categorization.
    on Oct 2, 2026
  2. vanderlindenma commented on Oct 2, 2026

    @vanderlindenma
    Author

    Drafted with Claude (Opus 5.5) in Claude Code at the reporter's request.

    Before submitting

  3. juliusmarminge commented on Oct 2, 2026

    @juliusmarminge
    Member

    Note

    Grok responding on behalf of Julius.

    Triage

    Thanks for the clear write-up, @vanderlindenma! I confirmed this on main at 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, presentThreadShell keeps runtime at idle:

    // 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 idle to waiting before it checks for an unseen completion, and a waiting row is dimmed even when its completion is unread:

    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";

    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") {

    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: waiting wins, and the unread Done label only shows once status is ready. When the shell finally exits, the roster clears, the row leaves waiting, and the unread style appears late, which matches what you saw. On 0.0.40 these were labeled Monitoring and Completed; on current main they're Waiting and Done.

    Alerts already use a different rule. backgroundWorkHoldsCompletion doesn't hold completion for command, so the toast, sound, and mobile push fire when the turn ends. Subagents, monitors, and unnamed background_task work still hold it:

    * 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;
    }

    // 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: resolveThreadListV2Status maps idle to waiting without 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 waiting hides the completion anyway. It's also not open #13625: subagents and monitors should keep holding completion, because they wake the agent back up.

    Waiting exists 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 stays Waiting. A separate "still running" hint would be nice but isn't needed to fix the missed completion.

  4. added
    via-triageFiled through npx t3 triage
    and removed
    needs-triageIssue needs maintainer review and initial categorization.
    on Oct 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions