Before submitting
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
- Open a thread using the Claude Code provider.
- 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).
- 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.
Before submitting
Area
apps/web
Summary
When a Claude Code foreground Bash call runs longer than ~2 s, Claude Code registers it as a
local_bashtask and emitstask_started/task_completedsystem messages for it. T3 ingests those astask.started/task.completedactivities, correctly stamps themagentKind: "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 ataskId" rather than on theagentKindstamp. 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
sleep 5 && exit 2(a non-zero exit makes the tint obvious, but the icon is wrong either way).Expected behavior
The task lifecycle row for a
local_bashtask 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 classifiedagentKind: "agent", matching what the Agents panel shows.Actual behavior
A separate collapsed row appears with the Lucide
Boticon, titled with the Bash call'sdescription, 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: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)
"bot"for any work entry with ataskId, regardless ofagentKind:t3code/apps/web/src/components/chat/MessagesTimeline.tsx
Lines 3062 to 3065 in 223ff44
entry.taskId→"agent-tool"→"bot"):t3code/packages/client-runtime/src/work-log/presentation.ts
Line 614 in 223ff44
t3code/apps/web/src/components/chat/MessagesTimeline.tsx
Lines 2150 to 2151 in 223ff44
local_bashon the non-agent list:t3code/packages/contracts/src/providerRuntime.ts
Lines 595 to 624 in 223ff44
isBackgroundTask, but nothing in the icon path consults it:t3code/packages/client-runtime/src/state/subagentRuntime.ts
Lines 119 to 121 in 223ff44
t3code/apps/web/src/session-logic.ts
Lines 620 to 622 in 223ff44
Claude Code side, for context: a foreground Bash that passes ~2 s of wall time is registered as a
local_bashtask (isBackgrounded: false) so it can offer backgrounding; thetask_startedpayload'sdescriptionis the Bash tool'sdescriptionargument, which is why title and detail are the same string.Suggested fix
In
workEntryIconNameandtoolGroupSummaryKind, treatentry.taskIdas agent chrome only when the entry is notisBackgroundTask(or when the payload'sagentKind === "agent"). Background task rows could use"terminal"whentaskTypeis inMONITOR_TASK_TYPES, or simply fall back to the tone icon. Alternatively, foldlocal_bashtask rows into the parent Bash row keyed bytoolUseId, since they carry no information the Bash row does not already have.Impact
Cosmetic issue
Version or commit
Server:
t30.0.39-nightly.20260905.1290. Source references above are againstmain @ 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.