Skip to content

[Bug]: Foreground Bash calls promoted to local_bash tasks render with the Bot (subagent) icon while the Agents panel correctly excludes them #10375

Description

@Luzivog

Before submitting

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

Area

apps/web

Summary

When a Claude Code foreground Bash call runs longer than ~2 s, Claude Code registers it as a local_bash task and emits task_started / task_completed system messages for it. T3 ingests those as task.started / task.completed activities, correctly stamps them agentKind: "background", and correctly keeps them out of the Agents panel. But the timeline renders the resulting work row with the Bot (robot) icon, because the icon picker keys on "has a taskId" rather than on the agentKind stamp. The user sees a robot row in the transcript, looks for the subagent in the Agents panel, and finds nothing.

So the two surfaces disagree: the Agents panel says "not an agent", the timeline row says "agent". The comment on the icon rule even says these rows are "subagent lifecycle rows", which is only true for agent-kind tasks.

Related but distinct: #9107 is about background-from-start shells being invisible. This report is about foreground shells that get task-promoted showing agent chrome they should not have.

Steps to reproduce

  1. Open a thread using the Claude Code provider.
  2. Have the agent run any foreground Bash command that takes more than ~2–3 s, e.g. sleep 5 && exit 2 (a non-zero exit makes the tint obvious, but the icon is wrong either way).
  3. Watch the timeline after the command finishes.

Expected behavior

The task lifecycle row for a local_bash task should render with the same terminal/command chrome as the Bash call it belongs to (or be folded into that Bash row). The Bot icon should be reserved for rows whose task is classified agentKind: "agent", matching what the Agents panel shows.

Actual behavior

A separate collapsed row appears with the Lucide Bot icon, titled with the Bash call's description, and with the same description repeated as the monospace detail line. On a failed exit the icon is tinted with the failed-tool colour, so it reads as an orange/red robot. The Agents panel shows no agent for it.

Activity sequence recorded for one such call (ids elided), all sharing one toolUseId:

tool.started    itemType=command_execution  title="Command run"            (Bash)
tool.updated    itemType=command_execution  status=inProgress
task.started    taskType=local_bash agentKind=background title=<Bash description>  detail=<Bash description>   (~3 s after tool.started)
task.completed  taskType=local_bash agentKind=background status=failed tone=error
tool.updated    itemType=command_execution  status=failed
tool.completed  itemType=command_execution  status=failed

The Bash row itself renders fine with the terminal icon. The extra robot row comes from the task.* pair.

Where the icon decision comes from (main @ 223ff44)

  • Icon picker falls through to "bot" for any work entry with a taskId, regardless of agentKind:
    // Subagent lifecycle rows (grouped by taskId) get agent identity chrome.
    if (workEntry.taskId) {
    return "bot";
    }
  • Group summary kind does the same (entry.taskId"agent-tool""bot"):
    if (entry.itemType === "collab_agent_tool_call" || entry.taskId) return "agent-tool";

    case "agent-tool":
    return "bot";
  • The server-side classification already exists and puts local_bash on the non-agent list:
    export const MONITOR_TASK_TYPES: ReadonlySet<string> = new Set([
    "monitor",
    "monitor_mcp",
    "local_bash",
    "shell",
    ]);
    /** Task types that are neither agents nor watch loops (plan-mode bookkeeping). */
    export const INERT_TASK_TYPES: ReadonlySet<string> = new Set(["plan", "dream"]);
    /**
    * Agent-vs-background classification, stamped by ingestion as `agentKind` so
    * persisted rows are self-describing. A deliberate denylist: the SDK's
    * agent-flavored type names drift (subagent, local_agent, local_workflow, …)
    * and an allowlist silently dropped real subagents when "local_agent"
    * appeared. A task launched from inside a subagent (agentId set) is
    * agent-internal background work UNLESS it is itself agent-flavored — a
    * nested agent can outlive its parent and stays in the roster.
    */
    export function classifyTaskAgentKind(input: {
    readonly taskType?: string | undefined;
    readonly agentId?: string | undefined;
    }): "agent" | "background" {
    const { taskType, agentId } = input;
    const nonAgentType =
    taskType !== undefined && (MONITOR_TASK_TYPES.has(taskType) || INERT_TASK_TYPES.has(taskType));
    if (agentId !== undefined && agentId.trim().length > 0) {
    return taskType === undefined || nonAgentType ? "background" : "agent";
    }
    return nonAgentType ? "background" : "agent";
    }
  • The client already reads that stamp for the Agents panel and marks the work entry isBackgroundTask, but nothing in the icon path consults it:
    export function isBackgroundTaskActivity(payload: Record<string, unknown>): boolean {
    return payload.agentKind !== "agent";
    }

    if (isTaskActivity && payload && isBackgroundTaskActivity(payload)) {
    entry.isBackgroundTask = true;
    }

Claude Code side, for context: a foreground Bash that passes ~2 s of wall time is registered as a local_bash task (isBackgrounded: false) so it can offer backgrounding; the task_started payload's description is the Bash tool's description argument, which is why title and detail are the same string.

Suggested fix

In workEntryIconName and toolGroupSummaryKind, treat entry.taskId as agent chrome only when the entry is not isBackgroundTask (or when the payload's agentKind === "agent"). Background task rows could use "terminal" when taskType is in MONITOR_TASK_TYPES, or simply fall back to the tone icon. Alternatively, fold local_bash task rows into the parent Bash row keyed by toolUseId, since they carry no information the Bash row does not already have.

Impact

Cosmetic issue

Version or commit

Server: t3 0.0.39-nightly.20260905.1290. Source references above are against main @ 223ff44.

Environment

Linux host running t3 serve, accessed from the web UI in a desktop browser. Claude Code provider (CLI 2.1.263), model claude-fable-5-1.

Workaround

None needed; expanding the row shows it is just the Bash description, and the Agents panel is correct. The confusion is purely the icon.

Activity

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