pingdotgg / pingdotgg/t3code

[Bug]: Bash tool output never renders in the web client, even when the turn is expanded

Open
#6,388 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
23k
Forks
5.9k
Avg merge
11h 14m
Merged PRs (30d)
357

Description

Before submitting
  • I searched existing issues for duplicates. Closest I found are #4076/#216 (command text duplicated in place of output — different field-selection bug), #3039 (running-turn flicker, fixed pre-0.33), and #4568 (scroll jump on expand) — none of them is "persisted tool.completed output is never rendered".
  • I included enough detail to reproduce/diagnose (server-side data verified byte-for-byte against Claude Code's own transcript).
Area

apps/web

Steps to reproduce
  1. Open a project in the web client, provider Claude Code.
  2. Ask for something that runs a single Bash command with a large stdout (mine was 16,704 chars).
  3. Let the turn finish. The assistant's closing text after the command was short (~800 chars).
  4. Find the turn in the timeline. It is collapsed behind a "Worked for …" row.
  5. Expand the turn and expand the "Command run" entry.

The command output is not shown at any point.

Expected behavior

Expanding the turn, then the "Command run" entry, shows the command's output (the tool_result content that the server persisted), or at least a truncated view of it with a way to see the rest.

Actual behavior

The expanded "Command run" entry renders only the command line and the short detail string. The 16,704-char output is not rendered anywhere in the UI — collapsed or expanded. The data is fully present server-side.

Secondary case from the same code path: when a settled turn has no terminal assistant message (tool-only turn, or the turn settles before/without a final assistant message entry), deriveTurnFolds hides every entry in the group, so the turn collapses to a bare "Worked for …" row with nothing behind it.

Impact

Major degradation or frequent failure

Version or commit

v0.0.33 @ 3b72d17cb. I re-read the relevant files on origin/main @ ac1264e2c — unchanged, so this still applies.

Environment
  • Server: Linux (t3 serve), SQLite state store
  • Client: web
  • Provider: Claude Code
Evidence

I diffed Claude Code's own session transcript (.jsonl) against the server's SQLite projections for the same turn. Metadata only — no message content left the box.

Transcript item chars Server-side counterpart chars Match
user text 50 projection_thread_messages (user) 50 yes
Bash tool_use tool.started / tool.updated activities yes
Bash tool_result 16704 projection_thread_activities tool.completeddata.result.content 16704 yes, exact
assistant text 800 projection_thread_messages (assistant) 800 yes

The tool.completed payload decodes as data.toolName="Bash", data.input.command (167 chars), data.detail (173 chars), data.result.content (16704 chars), data.result.is_error=false. Nothing is dropped, truncated or zero-length server-side — this is purely client rendering.

(When I asked the same question again ~76 min later, Claude re-emitted the same information as a 17,100-char plain assistant message, which the client did render. That path is projection_thread_messages, not projection_thread_activities.)

Candidate code path

Two independent layers, both in the web client. Line numbers are on origin/main @ ac1264e2c.

1. The tool result never reaches the work-log entry model. WorkLogEntry (apps/web/src/session-logic.ts:64-98) has detail, command, rawCommand, changedFiles, toolData — no field for the tool result. toDerivedWorkLogEntry (apps/web/src/session-logic.ts:802) only populates toolData when itemType === "mcp_tool_call" (lines 865-870); for command_execution the data.result object is simply not read. Worse, extractToolDetail (apps/web/src/session-logic.ts:1397-1424) sources detail exclusively from payload.detail, and for command entries isCommandToolDetail short-circuits it to null before even reaching the rawOutput fallback — so nothing in the client ever reads data.result.content. Consequently buildToolCallExpandedBody (apps/web/src/components/chat/MessagesTimeline.tsx:2038) can only ever emit the command, the detail, and changed files — that is exactly what I see when I expand.

2. Folding hides the tool entry, and hides everything when there is no terminal message. deriveTurnFolds (apps/web/src/components/chat/MessagesTimeline.logic.ts:318) builds hiddenEntryIds at lines 386-398:

const hiddenEntryIds = new Set<string>();
for (const entry of group.entries) {
  if (entry.id === group.terminalEntry?.id) {
    continue;
  }
  // Agent-spawn CTA rows never fold: ...
  if (entry.kind === "work" && entry.entry.agentSpawn !== undefined) {
    continue;
  }
  hiddenEntryIds.add(entry.id);
}

group.terminalEntry is only set for an assistant message entry in terminalAssistantMessageIds (line 369; derived via deriveTerminalAssistantMessageIds at 460). With terminalEntry === null, every non-agent-spawn entry — including the tool.completed entry — is hidden behind the fold row. MAX_VISIBLE_WORK_LOG_ENTRIES = 1 (MessagesTimeline.logic.ts:13, used at 522) then adds a second, independent collapse layer over consecutive work entries.

Layer 1 alone explains the missing output. Layer 2 explains why turns can collapse to nothing.

Workaround

None in the UI. The output is recoverable from the server's SQLite (projection_thread_activities, payload_jsondata.result.content) or from Claude Code's own .jsonl transcript.


Happy to send a small focused PR if wanted.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with WorkLogEntry and toDerivedWorkLogEntry in apps/web/src/session-logic.ts, then trace buildToolCallExpandedBody in apps/web/src/components/chat/MessagesTimeline.tsx and deriveTurnFolds in MessagesTimeline.logic.ts. Verify the persisted tool.completed result.content reaches the expanded work-log entry and that tool-only settled turns retain visible entries; reproduce with a large Bash command output and an expanded turn.

Written by the indexing model from the issue text.

Assessment

Tech stack
typescript
Domain
frontend
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.