anthropics / anthropics/anthropic-sdk-typescript

Memory tool: two edits to one file in the same turn silently lose one (BetaToolRunner runs tool calls concurrently; BetaLocalFilesystemMemoryTool has no serialization)

Abierto
#1,181 0 comentarios 0 reacciones 0 asignados Ver en GitHub
Lenguaje dominante
TypeScript
Estrellas
2.1k
Forks
403
Merge medio
1 d 21 h
PR fusionados (30 d)
8

Descripción

**SDK version:** `@anthropic-ai/sdk` 0.124.0; `src/lib/tools/BetaToolRunner.ts` and `src/tools/memory/node.ts` on `main` as of 2026-09-09 behave the same.
**Runtime:** Node v24.9.0, macOS 15.

`BetaToolRunner` executes every `tool_use` block of an assistant turn concurrently:

```ts
const toolResults = await Promise.all(
toolUseBlocks.map(async (toolUse) => {
...
const result = await tool.run(input, { toolUse, toolUseBlock: toolUse, signal: requestOptions?.signal });
...
}),
);
```

`BetaLocalFilesystemMemoryTool.str_replace` and `insert` are read-modify-write with no lock: read the file, compute the new content, `atomicWriteFile`. When two of them target the same memory file in one turn, both read the original content, both write, and the last write wins. Both return the success snippet, so neither the model nor the application learns that an edit was dropped.

Claude does issue several memory writes in a single turn (in one live session on `claude-opus-5` a single turn carried five, across different files). Two `str_replace` calls on the same file are the natural way for it to make two corrections to one note at once, and nothing in the tool description discourages that.

### Repro

The `Promise.all` below is the interleaving the runner produces.

```ts
import fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import { BetaLocalFilesystemMemoryTool } from '@anthropic-ai/sdk/tools/memory/node';

const base = await fs.mkdtemp(path.join(os.tmpdir(), 'memory-repro-'));
const memory = await BetaLocalFilesystemMemoryTool.init(base);
await memory.create({ command: 'create', path: '/memories/race.md', file_text: 'alpha\nbeta\n' });

console.log(await Promise.all([
memory.str_replace({ command: 'str_replace', path: '/memories/race.md', old_str: 'alpha', new_str: 'ALPHA' }),
memory.str_replace({ command: 'str_replace', path: '/memories/race.md', old_str: 'beta', new_str: 'BETA' }),
]));
console.log(JSON.stringify(await fs.readFile(path.join(base, 'memories/race.md'), 'utf8')));
```

Output:

```
[
"The memory file has been edited. Here is the snippet showing the change (with line numbers):\n 1\tALPHA\n 2\tbeta\n 3\t",
"The memory file has been edited. Here is the snippet showing the change (with line numbers):\n 1\talpha\n 2\tBETA\n 3\t"
]
"ALPHA\nbeta\n"
```

Two successes reported, one edit on disk.

### Expected

Both edits applied: `"ALPHA\nBETA\n"`.

### Suggested fix

Either change closes it, and both together are cheap:

- In `BetaLocalFilesystemMemoryTool`, run commands through a per-instance promise queue so they execute one at a time in call order. The operations are local filesystem calls, so serialising them costs nothing noticeable.
- In `BetaToolRunner`, execute the `tool_use` blocks of a turn sequentially, or give `BetaRunnableTool` an opt-in for ordered execution, so any stateful client tool is safe. The Python SDK's runner already executes them in a `for` loop.

Guía de contribución

Abrir la guía de contribución

Línea de trabajo

Start with src/lib/tools/BetaToolRunner.ts and src/tools/memory/node.ts, then run the supplied Promise.all reproduction against the current main behavior. Verify that two edits to the same memory file both persist as "ALPHA\nBETA\n" and that the runner/tool path no longer reports success after dropping an edit.

Escrito por el modelo de indexación a partir del texto del issue.

Evaluación

Stack tecnológico
typescript
Área
tooling
Tipo de issue
Error
Dificultad
4/5
Tiempo estimado
3-5 días
Estado de actividad
Activo
Claridad
Bien especificado
Aptitud para principiantes
58/100

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.