From 57f936af63ae277f9e983529f797222819e7a096 Mon Sep 17 00:00:00 2001 From: iris Date: Wed, 1 Jul 2026 17:06:12 +0200 Subject: [PATCH] fix(agent): strip tool_use_error tags and render errors in red MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When a tool call fails, claude wraps the result text in ... XML tags. The terminal was displaying these raw, producing output like: 'File has not been read yet.' Fix renderToolResult in app.js: - Check c.is_error on the tool_result content block. - Strip the ... wrapper from the text. - Render error results with a '✗' prefix under '.tool-result.error' (flat, ≤120c) or '.tool-result-block.error' (
, longer text). Add .live .tool-result.error { color: var(--red); } to terminal.css so error results are visually distinct (red, same as turn-end-fail). Update terminal-rendering.md row taxonomy to document the two new error row classes. Closes #2104. --- docs/terminal-rendering.md | 2 ++ frontend/packages/agent/src/app.js | 17 ++++++++++++++++- frontend/packages/shared/src/terminal.css | 3 ++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/docs/terminal-rendering.md b/docs/terminal-rendering.md index 8bed16a3..6b060c76 100644 --- a/docs/terminal-rendering.md +++ b/docs/terminal-rendering.md @@ -63,6 +63,8 @@ parent's negative pull. | `.tool-result` (flat) | `← ` | muted | short `tool_result` (≤120c, non-recv) | stream-json | | `.tool-result-block` `
` | `Nl · headline` | muted, body is text | long generic `tool_result` | stream-json | | `.tool-result-block` `
` | `recv ← ` | muted, body is markdown | `tool_result` correlated to a prior `recv` tool_use via id | stream-json | +| `.tool-result.error` (flat) | `✗ ` | red | `tool_result` with `is_error: true` (≤120c); `` wrapper stripped | stream-json | +| `.tool-result-block.error` `
` | `Nl · headline` | red, body is text | long error `tool_result` (`is_error: true`); wrapper stripped | stream-json | | `.tool-use` | `⌁ task started · [type]` | cyan | claude Task-tool subagent start (dead path — `Task` omitted from agent allow-list) | `renderTaskEvent` | | `.turn-end-ok` / `.turn-end-fail` / `.tool-result` | `⌁ task ✓/✗/◌ · · → ` | green / red / muted | claude Task-tool result (dead path for agents) | `renderTaskEvent` | | `.note` | `· ` | muted | harness chatter | `LiveEvent::Note` | diff --git a/frontend/packages/agent/src/app.js b/frontend/packages/agent/src/app.js index afd9abc9..12a2085a 100644 --- a/frontend/packages/agent/src/app.js +++ b/frontend/packages/agent/src/app.js @@ -1684,9 +1684,16 @@ window.marked = marked; // grows with the session — entries are tiny strings. const toolNameById = new Map(); function renderToolResult(c, api) { - const txt = Array.isArray(c.content) + const rawTxt = Array.isArray(c.content) ? c.content.map(p => p.text || '').join('') : (c.content || ''); + // Strip the wrapper that claude + // emits when a tool call fails — the tags are an implementation detail + // and add noise to the terminal display. + const isError = !!c.is_error; + const txt = isError + ? rawTxt.replace(/^([\s\S]*)<\/tool_use_error>$/, '$1').trim() + : rawTxt; const sourceName = c.tool_use_id ? toolNameById.get(c.tool_use_id) : null; const isMessageBearing = sourceName === 'mcp__hyperhive__recv'; // When an ask's tool_result lands the broker has just @@ -1707,6 +1714,14 @@ window.marked = marked; const headline = trimmed.slice(0, 90) + '…'; return `${lines}L · ${headline}`; })(); + if (isError) { + if (!txt.trim() || txt.length <= 120) { + api.row('tool-result error', '✗ ' + summaryBody); + } else { + api.details('tool-result-block error', summaryBody, txt); + } + return; + } // Flat row: keep the `←` glyph in the prefix column. Details rows // drop it — the `▸/▾` disclosure marker sits in that column via CSS. if (isMessageBearing && txt.trim()) { diff --git a/frontend/packages/shared/src/terminal.css b/frontend/packages/shared/src/terminal.css index 4beb8a1c..48091f81 100644 --- a/frontend/packages/shared/src/terminal.css +++ b/frontend/packages/shared/src/terminal.css @@ -120,7 +120,8 @@ .live .text { color: var(--fg); } .live .thinking { color: var(--muted); font-style: italic; } .live .tool-use { color: var(--cyan); } -.live .tool-result { color: var(--muted); } +.live .tool-result { color: var(--muted); } +.live .tool-result.error { color: var(--red); } .live .result { color: var(--green); } .live .note { color: var(--muted); } /* Distinguish stderr lines (orange) and operator-initiated notes