anthropics / anthropics/claude-agent-sdk-typescript
Tool-result budget enforcement (nZi) throws "e.reduce is not a function" on valid MCP array content, silently terminating the run (0.3.185)
- Lingua principale
- Shell
- Stelle
- 1.8k
- Fork
- 226
- Metriche di merge delle PR
- Nessuna PR unita negli ultimi 30g
Descrizione
## Summary
On `@anthropic-ai/claude-agent-sdk@0.3.185`, the per-message **tool-result budget enforcement** throws `e.reduce is not a function` while computing the size of a **valid** MCP tool result, replaces the tool result the agent receives with that error string, and the agent then **silently stops producing a final result** (no summary output).
It is **non-deterministic** across sessions for the exact same tool + input shape, which points at a server-side feature flag gating the enforcement path.
## Error
The tool result content handed to the model is replaced with:
```
e.reduce is not a function. (In 'e.reduce((t,n)=>t+(n.type==="text"?n.text.length:0),0)', 'e.reduce' is undefined)
```
(The `(In '...')` suffix is the JavaScriptCore/Bun error format, i.e. it is thrown inside the bundled native CLI binary, not in `sdk.mjs`.)
## What we verified
Our custom MCP server (SSE transport) returns a **perfectly standard** `CallToolResult` for the call that triggers this:
- `content: [{ "type": "text", "text": "" }]` — a proper content-block **array**, `text` is a **string**
- `isError: false`
- ~2.4 KB (well under any token cap — not an oversized-output case)
- No exception on the server side
We confirmed this directly from the MCP server's own debug log (the raw `JSONRPCResponse` it sent over SSE). So the input to the SDK is a valid content array, yet the SDK throws `e.reduce is not a function`, which can only happen if the value being reduced is **not an array**.
When it fires, the agent run ends with no assistant final message (silent termination), which is hard to diagnose because nothing is thrown to the caller — the error is delivered *as* the tool result.
## Root cause (from inspecting the native binary)
`strings` on the bundled `claude-agent-sdk-{platform}/claude` binary shows the throwing function:
```js
function nZi(e){
if (typeof e === "string") return e.length;
return e.reduce((t,n)=>t+(n.type==="text"?n.text.length:0),0);
}
```
`nZi` computes a tool_result's content size and is called from the per-message tool-result **budget enforcement** chain:
```
nZi ← L$d ← rZi ← U$d (oZi)
```
- The budget threshold is `N$i = 200000` chars.
- Enforcement is gated by a feature flag (`tengu_hawthorn_steeple`); the state object is only built when the flag is on, so on sessions where the flag is **off**, `nZi` never runs and the same tool result is processed without error — this fully explains the non-determinism we observe.
- `nZi` assumes content is either a `string` or an **array**. If a tool_result's `content` reaches it as a **non-array object**, `e.reduce` is `undefined` → it throws.
Since our MCP server demonstrably returns a valid `[{type:"text", text:string}]` array, the non-array object must be produced **inside the SDK's own message bookkeeping** under budget pressure (e.g. during persist/replace or normalization), not by the MCP server.
## Suggested fix
Guard `nZi` against non-array, non-string content so a single malformed/normalized entry can't crash the whole enforcement pass (and silently kill the run):
```js
function nZi(e){
if (typeof e === "string") return e.length;
if (!Array.isArray(e)) return 0; // or: JSON.stringify(e).length
return e.reduce((t,n)=>t+(n.type==="text"?n.text.length:0),0);
}
```
Alternatively, normalize/skip non-array tool_result content in `L$d` before it reaches `nZi`. Either way, a budget-enforcement bookkeeping error should not be surfaced *as the tool result* and should not silently terminate the run — it would be far easier to diagnose if it were logged/thrown to the caller instead.
## Environment
- `@anthropic-ai/claude-agent-sdk@0.3.185`
- Custom MCP server over SSE transport
- Large/long conversation (enough to engage the tool-result budget enforcement path)
- Orchestrator + sub-agent (Task) setup; the failing tool call runs inside a sub-agent
Guida per i contributori
Nessuna guida per i contributori indicizzata per questo repository
Valutazione
Questa issue non è ancora stata valutata.