anthropics / anthropics/anthropic-sdk-typescript

Bug: `toolRunner`'s `setMessagesParams` causes duplicate tool call loops

Aberta
#964 3 comentários 1 reação 0 responsáveis Ver no GitHub
bug code-change
Linguagem predominante
TypeScript
Estrelas
2.1k
Forks
403
Merge médio
1d 21h
PRs com merge (30d)
8

Descrição

### Summary

Two related bugs when using `client.beta.messages.toolRunner` with `code_execution_20260120` and client-side tools (`betaTool`):

1. **`container.id` is not automatically forwarded** from one iteration to the next, causing a hard error when client-side tool results are sent back.
2. **Using `setMessagesParams` to manually set `container.id`** as a workaround causes the model to enter an infinite duplicate tool-calling loop.

---

### Environment

- `@anthropic-ai/sdk` version: 0.80.0
- Node.js version: v22.14.0
- Model: `claude-opus-4-6` or `claude-sonnet-4-6`

---

### Reproduction

```typescript
import "dotenv/config";
import Anthropic from "@anthropic-ai/sdk";
import { betaTool } from "@anthropic-ai/sdk/helpers/beta/json-schema";

const client = new Anthropic();

const calculateSumTool = betaTool({
name: "calculate_sum",
description: "Add two numbers together",
inputSchema: {
type: "object",
properties: {
a: { type: "number", description: "First number" },
b: { type: "number", description: "Second number" },
},
required: ["a", "b"],
},
run: async (input) => String(input.a + input.b),
});

const runner = client.beta.messages.toolRunner({
model: "claude-opus-4-6",
max_tokens: 1024,
tools: [
{ ...calculateSumTool, allowed_callers: ["code_execution_20260120"] },
{ type: "code_execution_20260120", name: "code_execution" },
],
messages: [{ role: "user", content: "What's the sum of 5 and 3?" }],
stream: true,
});

for await (const messageStream of runner) {
for await (const event of messageStream) {}

const iterationMessage = await messageStream.finalMessage();
console.log("Container: ", iterationMessage.container);
console.log("Output: ", iterationMessage.content);

const toolResult = await runner.generateToolResponse();
console.log("Tool Result", toolResult);

// could be a tmp or permanent fix -> causes infinite tool call loop
if (iterationMessage.container?.id) {
runner.setMessagesParams((params) => ({
...params,
container: iterationMessage.container!.id, // passing string directly
}));
}
}
```

---

### Bug 1: Missing `container.id` propagation

After iteration A completes, `iterationMessage.container` contains a valid `container.id`. However, when `runner.generateToolResponse()` triggers the next request, the `container.id` is **not included**, causing:

```json
{
"type": "invalid_request_error",
"message": "container_id is required when there are pending tool uses generated by code execution with tools."
}
```

**Expected behavior:** `toolRunner` should automatically carry `container.id` from the previous iteration's response into the next request, the same way it already stitches `tool_use` / `tool_result` message pairs.

---

### Bug 2: `setMessagesParams` workaround — two failure modes

Since the runner doesn't handle this automatically, the only recourse is `setMessagesParams`. Both available approaches are broken:

**Approach A — pass an object:**
```typescript
runner.setMessagesParams((params) => ({
...params,
container: {
id: iterationMessage.container?.id,
skills: [],
},
}));
```
Fails immediately with:
```json
{
"type": "invalid_request_error",
"message": "container: Input should be a valid string"
}
```
This is surprising — the `container.id` logged just before the call is a valid string (e.g. `container_011CZZfD7NfLQumhoWHGwYmw`), yet the API rejects it. It's unclear whether `setMessagesParams` is serializing the field incorrectly, or whether the API's `container` field type contract differs from the SDK's TypeScript types.

**Approach B — pass the string directly:**
```typescript
runner.setMessagesParams((params) => ({
...params,
container: iterationMessage.container!.id,
}));
```
Does not error, but causes an **infinite tool-calling loop**. The model repeatedly calls `code_execution` → `calculate_sum` with identical inputs and near-identical outputs, never terminating:

```
Container: { id: 'container_011CZZfD7NfLQumhoWHGwYmw', expires_at: '...' }
Output: [ { type: 'server_tool_use', name: 'code_execution', input: { code: '...calculate_sum({"a": 5, "b": 3})...' } },
{ type: 'tool_use', name: 'calculate_sum', input: { a: 5, b: 3 } } ]
Tool Result { role: 'user', content: [{ type: 'tool_result', tool_use_id: '...', content: '8' }] }
DEBUG: container_011CZZfD7NfLQumhoWHGwYmw
// ... repeats indefinitely with new tool IDs but identical inputs/outputs
```

This strongly suggests that `setMessagesParams` is corrupting or resetting the accumulated message history between iterations, causing the model to lose context of what it has already executed.

---

### Additional context

The two bugs compound each other completely: Bug 1 makes `container.id` forwarding mandatory for this use case, and Bug 2 means both available workarounds either hard-error or loop infinitely. There is currently no working path to use client-side `betaTool`s alongside `code_execution_20260120` in a streaming `toolRunner` loop.

---

Guia de contribuição

Abrir o guia de contribuição

Avaliação

Esta issue ainda não foi avaliada.

Receba novas issues na sua caixa de entrada

Um resumo curto de issues do GitHub para quem está começando.