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)

Aperta
#358 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub
bug
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.

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.