[BUG] Fullscreen TUI: failed Bash tool calls drop the entire ⎿ result row (stdout+stderr present in JSONL and tool_result; success path OK)

Open 💬 0 comments Opened Jul 15, 2026 by genesiscz

Preflight Checklist

  • [x] I have searched existing issues and this hasn't been reported yet
  • [x] This is a single bug report (please file separate reports for different bugs)
  • [x] I am using the latest version of Claude Code

What's Wrong?

In fullscreen TUI (/tuiCurrent renderer: fullscreen), when a Bash tool call exits non-zero, the live UI shows only the command header:

⏺ Bash(echo "STDOUT-MARKER-ABC" && echo "STDERR-MARKER-XYZ" >&2 && exit 1)

There is no body line at all — not the captured stdout, not the stderr, not even the bare word Error / Error: Exit code 1. Successful Bash calls in the same session (e.g. echo "hello") render their body correctly.

This is a pure display-layer bug:

| Layer | Status |
|-------|--------|
| Command execution | ✅ runs (exit 1 as expected) |
| stdout + stderr capture | ✅ both streams present |
| Session JSONL tool_result | ✅ full content + is_error: true |
| Model-visible tool_result | ✅ model receives and acts on the output |
| Fullscreen TUI render | ❌ drops the entire row |

Conditional on failure only. Non-zero exit → missing . Exit 0 → body shown.

Conditional on fullscreen renderer only. Classic/default TUI (/tui default) correctly shows:

⏺ Bash(echo "This command fails" && exit 1)
  ⎿  Error: Exit code 1
     This command fails
Why this is not just a “me too” on #64474

#64474 (open, 2026-06-01) reports that expanding a failed Bash call shows the command but no output. Our observed failure is worse and more user-hostile:

  1. The result row is completely absent — not “empty when expanded,” but never rendered.
  2. There is nothing to expand; the tool call collapses to the header line alone.
  3. We prove the data path is intact (JSONL + model tool_result below), so this cannot be blamed on capture, sandbox, or empty-command output.
  4. First confirmed here on 2.1.170 (after #64474’s original 2.1.159 repro) and still broken on 2.1.185 — if anything the render path got worse, not better.

Closest prior art is #64474 (open; “empty when expanded”). Filing separately because our repro is stronger (entire row missing), we have end-to-end harness/JSONL/model proof, and we document a hook-based workaround. Not a duplicate of the ENOSPC cluster (#65880 / #63909) or the H.replace TypeError (#63825).

No maintainer engagement on #64474 as of last check (2026-07-15); last human comment 2026-06-23. Filing with a full forensic chain so this doesn’t depend on a low-engagement thread.

What Should Happen?

Failed Bash tool calls in fullscreen TUI must render the same information classic TUI already shows:

⏺ Bash(echo "STDOUT-MARKER-ABC" && echo "STDERR-MARKER-XYZ" >&2 && exit 1)
  ⎿  Error: Exit code 1
     STDOUT-MARKER-ABC
     STDERR-MARKER-XYZ

At minimum: a visible row with exit code and combined stdout/stderr (the same strings already stored in the session JSONL and already sent to the model). Success and failure paths should share the same render pipeline for the body; only styling (e.g. error color) should differ.

Error Messages/Logs

No CLI error. The TUI silently omits the result row. No stack trace, no “render failed” banner for the Bash body itself.

What the model received (same turn):

Exit code 1
STDOUT-MARKER-ABC
STDERR-MARKER-XYZ

What the session JSONL recorded (real session under ~/.claude/projects/.../<session>.jsonl, CC 2.1.170, Ghostty, fullscreen):

"content": [{
  "type": "tool_result",
  "content": "Exit code 1\nSTDOUT-MARKER-ABC\nSTDERR-MARKER-XYZ",
  "is_error": true,
  "tool_use_id": "toolu_011vzNHQr4967B7e5RZdFo1b"
}],
"toolUseResult": "Error: Exit code 1\nSTDOUT-MARKER-ABC\nSTDERR-MARKER-XYZ"

Both markers present. is_error: true. The harness did its job; only the fullscreen paint path drops the body.

What the user saw (verbatim):

⏺ Bash(echo "STDOUT-MARKER-ABC" && echo "STDERR-MARKER-XYZ" >&2 && exit 1)

Nothing under it. No . No Error.

Steps to Reproduce

  1. Open an interactive Claude Code session.
  2. Confirm renderer is fullscreen:

``
/tui
`
Expect:
Current renderer: fullscreen. If not, switch with /tui fullscreen`.

  1. Ask the model (or run via Bash tool) this exact command:

``bash
echo "STDOUT-MARKER-ABC" && echo "STDERR-MARKER-XYZ" >&2 && exit 1
``

  1. Observe the live tool-call card.

Actual (fullscreen): only the ⏺ Bash(...) header; no row.

Control A — success path (same session, still fullscreen):

echo "hello"

⎿ hello appears. So capture + success render work.

Control B — failure path on classic renderer:

/tui default

Re-run the failing command → ⎿ Error: Exit code 1 + both markers appear. So the classic renderer is fine; bug is fullscreen-specific.

Control C — prove data integrity without relying on the UI:
Inspect the matching tool_result in the session JSONL (or ask the model what exit code / markers it saw). Content is present; only display is missing.

Claude Model

Not sure / Multiple models

(Model-independent — pure TUI renderer bug. Observed under Opus / Fable sessions; harness + display, not model generation.)

Is this a regression?

I don't know

(Unknown first-bad version. Still broken from at least 2.1.159 [#64474] through 2.1.170 and 2.1.185. Classic renderer remains correct on the same builds, so this is not “Bash stopped returning output.”)

Last Working Version

Unknown for fullscreen. Classic/default TUI is the working path on all versions tested.

Claude Code Version

2.1.170 — first forensic capture (JSONL + markers)
2.1.185 — live retest, ⎿ still completely absent in fullscreen
2.1.206 — current install at report time (`claude --version`)

#64474 original report: 2.1.159. No fix called out in changelog through published versions checked against this family (through at least 2.1.207).

Platform

Anthropic API

(Claude.ai / Max subscription interactive sessions; not Bedrock/Vertex-specific.)

Operating System

macOS

  • Darwin 25.3.0, arm64 (Apple Silicon)
  • Confirmed by a third party on Linux as well (#64474 comment: still broken on 2.1.186) — not macOS-only.

Terminal/Shell

Other

  • Ghostty 1.3.2 / TERM=xterm-256color / zsh
  • Original #64474: iTerm2 — same class of bug, different terminal → not Ghostty-specific.

Additional Information

Impact

Any non-zero Bash exit (build failure, test failure, set -e pipeline, missing file, script crash) is invisible in the UI under the default-for-many-users fullscreen renderer. The model still “knows” and may narrate the failure correctly, but the user cannot audit the real stderr without:

  • switching to /tui default, or
  • digging in the session JSONL, or
  • re-running via ! … REPL prefix / tee to a file.

This makes debugging agent sessions actively hostile: the most important tool results (failures) are the ones hidden.

Related issues (classified, not dups of this ticket)

| Issue | Relation |
|-------|----------|
| #64474 | Same surface (fullscreen + failed Bash). Our report is the stronger “row missing entirely” + forensic + workaround variant. |
| #63825 | Different mechanism (TypeError / H.replace on sandbox-violation annotation). Failure-class events can lose body, but not the same renderer branch. |
| #65880 / #63909 / #65166 / #68910 / #68383 | ENOSPC / statfs cluster — replaces empty-stdout+nonzero with a false “temp filesystem full” string. Opposite of blank: wrong string, not missing row. |
| #63966 | Earlier report: tool results empty then flush late/out-of-order (parallel/delivery). Different bug. |
| #27886 / #34713 | False “hook error” labels — can crowd the failure UI but do not swallow Bash stderr. |

Working workaround: PostToolUseFailuresystemMessage

The hook fires after the tool result is already captured. systemMessage still renders as a visible TUI warning even when the fullscreen path drops the row. Confirmed working in fullscreen on 2026-06-23.

Minimal hook:

#!/usr/bin/env bun
// posttool-failure-hook.ts — surfaces failed Bash output via systemMessage
const hookData = JSON.parse(await Bun.stdin.text());
// On failure events, actual output is in `error` (toolResponse is often null/empty)
const error = typeof hookData.error === "string" ? hookData.error : "";
console.log(
  JSON.stringify({
    suppressOutput: false,
    systemMessage: error ? `Tool failed — output: ${error}` : "Tool failed",
    hookSpecificOutput: { hookEventName: "PostToolUseFailure" },
  }),
);
process.exit(0);

Wire in ~/.claude/settings.json:

{
  "hooks": {
    "PostToolUseFailure": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "bun /path/to/posttool-failure-hook.ts"
          }
        ]
      }
    ]
  }
}

Note: updatedToolOutput is not available on PostToolUseFailure (success-path PostToolUse only). systemMessage is the user-visible channel that works around the broken paint path.

Immediate UI workaround
/tui default

Classic renderer shows failed-Bash bodies correctly on the same versions where fullscreen drops them.

Diagnostic signature for others
  1. /tuifullscreen
  2. Run any non-zero-exit Bash with known stdout markers
  3. UI: header only, no
  4. Ask model “what was the exit code / last line of that command?” → answers correctly from tool_result
  5. rg "STDOUT-MARKER" ~/.claude/projects/.../<session>.jsonl → hit in tool_result.content

If (3) fails but (4)+(5) pass → this bug, not capture/ENOSPC/TypeError.

Ask

Please fix the fullscreen TUI failure branch so non-zero Bash results paint the same body classic already paints. The data is already in the render pipeline’s input (JSONL + model tool_result); only the failure-path paint is missing. Happy to attach a redacted JSONL excerpt or a screen recording if useful.

View original on GitHub ↗