fix: prevent null output and string render crashes in MessagesBoundary

UserToolSuccessMessage now requires parsedOutput.success before trusting
data, and guards toolResult against non-object values before calling
renderToolResultMessage. String renderedMessage is wrapped in <Text> so
multi-line tool reports (e.g. GoalTool usage report) don't crash Ink.

Defense in depth added to VaultHttpFetchTool/UI (matches the existing
pattern in LocalMemoryRecallTool and GoalTool) and to the
mapToolResultToToolResultBlockParam of both vault/memory tools.

Co-Authored-By: glm-5.2 <zai-org@claude-code-best.win>
This commit is contained in:
claude-code-best
2026-06-26 05:47:52 +08:00
parent 0753dafccc
commit 8246ffa392
6 changed files with 24 additions and 6 deletions

View File

@@ -67,7 +67,14 @@ export function UserToolSuccessMessage({
if (parsedOutput && !parsedOutput.success) {
return null;
}
const toolResult = parsedOutput?.data ?? message.toolUseResult;
// Only trust schema-validated output. Fall back to raw toolUseResult only
// when it's a non-null object — schemas without outputSchema, or successful
// parses that yield null/undefined data, must not reach renderToolResultMessage
// (tool UIs access output.error / output.action on first line and crash).
const toolResult = parsedOutput?.success ? parsedOutput.data : message.toolUseResult;
if (!toolResult || typeof toolResult !== 'object') {
return null;
}
// Collapse diff display for old messages (verbose/ctrl+o overrides)
const effectiveStyle = shouldCollapseDiffs && !verbose ? 'condensed' : style;
@@ -88,6 +95,11 @@ export function UserToolSuccessMessage({
return null;
}
// Ink requires text strings to be inside <Text>. Tools that return plain
// multi-line strings (e.g. GoalTool's usage report) crash without the wrap.
// React elements from UI.tsx files pass through unchanged.
const wrappedMessage = typeof renderedMessage === 'string' ? <Text>{renderedMessage}</Text> : renderedMessage;
// Tools that return '' from userFacingName opt out of tool chrome and
// render like plain assistant text. Skip the tool-result width constraint
// so MarkdownTable's SAFETY_MARGIN=4 (tuned for the assistant-text 2-col
@@ -97,7 +109,7 @@ export function UserToolSuccessMessage({
return (
<Box flexDirection="column">
<Box flexDirection="column" width={rendersAsAssistantText ? undefined : width}>
{renderedMessage}
{wrappedMessage}
{feature('BASH_CLASSIFIER')
? classifierRule && (
<MessageResponse height={1}>