Skip to content

Commit 8246ffa

Browse files
claude-code-bestglm-5.2
andcommitted
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>
1 parent 0753daf commit 8246ffa

6 files changed

Lines changed: 24 additions & 6 deletions

File tree

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "claude-code-best",
3-
"version": "2.8.1",
3+
"version": "2.8.2",
44
"description": "Reverse-engineered Anthropic Claude Code CLI — interactive AI coding assistant in the terminal",
55
"type": "module",
66
"author": "claude-code-best <claude-code-best@proton.me>",

packages/builtin-tools/src/tools/GoalTool/GoalTool.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,10 @@ export const GoalTool = buildTool({
137137
return `Updating goal: ${input.status}${input.reason ? ` — ${input.reason}` : ''}`
138138
},
139139
renderToolResultMessage(output: Output) {
140-
if (output.error) return `Goal error: ${output.error}`
140+
if (!output) {
141+
return null
142+
}
143+
if (output?.error) return `Goal error: ${output.error}`
141144
if (output.report) return output.report
142145
if (output.goal) {
143146
return `Goal "${output.goal.objective}" — ${output.goal.status}`

packages/builtin-tools/src/tools/LocalMemoryRecallTool/LocalMemoryRecallTool.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -547,7 +547,7 @@ export const LocalMemoryRecallTool = buildTool({
547547
type: 'tool_result',
548548
tool_use_id: toolUseID,
549549
content: jsonStringify(output),
550-
is_error: output.error !== undefined,
550+
is_error: output?.error !== undefined,
551551
}
552552
},
553553
} satisfies ToolDef<InputSchema, Output>)

packages/builtin-tools/src/tools/VaultHttpFetchTool/UI.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ export function renderToolResultMessage(
3333
_progressMessagesForMessage: ProgressMessage<ToolProgressData>[],
3434
{ verbose }: { verbose: boolean },
3535
): React.ReactNode {
36+
// Defense in depth: framework validates via outputSchema, but resumed
37+
// transcripts can still produce null here via deserialization edge cases.
38+
if (!output) return null;
3639
if (output.error) {
3740
return (
3841
<MessageResponse height={1}>

packages/builtin-tools/src/tools/VaultHttpFetchTool/VaultHttpFetchTool.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -409,7 +409,7 @@ export const VaultHttpFetchTool = buildTool({
409409
type: 'tool_result',
410410
tool_use_id: toolUseID,
411411
content: jsonStringify(output),
412-
is_error: output.error !== undefined,
412+
is_error: output?.error !== undefined,
413413
}
414414
},
415415
} satisfies ToolDef<InputSchema, Output>)

src/components/messages/UserToolResultMessage/UserToolSuccessMessage.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,14 @@ export function UserToolSuccessMessage({
6767
if (parsedOutput && !parsedOutput.success) {
6868
return null;
6969
}
70-
const toolResult = parsedOutput?.data ?? message.toolUseResult;
70+
// Only trust schema-validated output. Fall back to raw toolUseResult only
71+
// when it's a non-null object — schemas without outputSchema, or successful
72+
// parses that yield null/undefined data, must not reach renderToolResultMessage
73+
// (tool UIs access output.error / output.action on first line and crash).
74+
const toolResult = parsedOutput?.success ? parsedOutput.data : message.toolUseResult;
75+
if (!toolResult || typeof toolResult !== 'object') {
76+
return null;
77+
}
7178

7279
// Collapse diff display for old messages (verbose/ctrl+o overrides)
7380
const effectiveStyle = shouldCollapseDiffs && !verbose ? 'condensed' : style;
@@ -88,6 +95,11 @@ export function UserToolSuccessMessage({
8895
return null;
8996
}
9097

98+
// Ink requires text strings to be inside <Text>. Tools that return plain
99+
// multi-line strings (e.g. GoalTool's usage report) crash without the wrap.
100+
// React elements from UI.tsx files pass through unchanged.
101+
const wrappedMessage = typeof renderedMessage === 'string' ? <Text>{renderedMessage}</Text> : renderedMessage;
102+
91103
// Tools that return '' from userFacingName opt out of tool chrome and
92104
// render like plain assistant text. Skip the tool-result width constraint
93105
// so MarkdownTable's SAFETY_MARGIN=4 (tuned for the assistant-text 2-col
@@ -97,7 +109,7 @@ export function UserToolSuccessMessage({
97109
return (
98110
<Box flexDirection="column">
99111
<Box flexDirection="column" width={rendersAsAssistantText ? undefined : width}>
100-
{renderedMessage}
112+
{wrappedMessage}
101113
{feature('BASH_CLASSIFIER')
102114
? classifierRule && (
103115
<MessageResponse height={1}>

0 commit comments

Comments
 (0)