anthropics / anthropics/anthropic-sdk-typescript
BetaLocalFilesystemMemoryTool.str_replace: a multi-line old_str never matches, and an omitted new_str writes the word "undefined"
- Linguagem predominante
- TypeScript
- Estrelas
- 2.1k
- Forks
- 403
- Merge médio
- 1d 21h
- PRs com merge (30d)
- 8
Descrição
**SDK version:** `@anthropic-ai/sdk` 0.124.0; the same code is in `src/tools/memory/node.ts` on `main` as of 2026-09-09.
**Runtime:** Node v24.9.0, macOS 15.
`BetaLocalFilesystemMemoryTool.str_replace` has two defects. Both surface with real `str_replace` calls Claude makes when it uses the memory tool.
### 1. A multi-line `old_str` never matches
The uniqueness check walks the file line by line and tests each line with `includes`:
```ts
const lines = content.split('\n');
const matchingLines: number[] = [];
lines.forEach((line, index) => {
if (line.includes(command.old_str)) {
matchingLines.push(index + 1);
}
});
if (matchingLines.length === 0) {
throw new Error(`No replacement was performed, old_str \`${command.old_str}\` did not appear verbatim in ${command.path}.`);
}
```
No single line can contain a string with a newline in it, so any `old_str` that spans lines is reported as absent even when the file contains it verbatim. Claude does send multi-line `old_str` values: in a live session on `claude-opus-5` it issued `old_str: "## Notes\n- (none yet)"` against a file that contained exactly that text, received the "did not appear verbatim" error, and retried with a single-line match. The Python SDK's `BetaLocalFilesystemMemoryTool` counts occurrences over the whole content (`content.count(command.old_str)`), so it does not have this problem.
### 2. An omitted `new_str` writes the word `undefined` into the file
The memory tool documentation says `new_str` is optional for `str_replace`: "when it's omitted, `old_str` is deleted without a replacement." The implementation does `content.replace(command.old_str, command.new_str)`, so a missing `new_str` writes the literal string `undefined` and reports success.
### Repro
```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);
// 1. multi-line old_str, present verbatim
await memory.create({ command: 'create', path: '/memories/notes.md', file_text: 'line1\nline2\nline3\n' });
await memory.str_replace({ command: 'str_replace', path: '/memories/notes.md', old_str: 'line1\nline2', new_str: 'joined' });
// Error: No replacement was performed, old_str `line1
// line2` did not appear verbatim in /memories/notes.md.
// 2. new_str omitted
await memory.create({ command: 'create', path: '/memories/omit.md', file_text: 'keep DELETEME keep\n' });
await memory.str_replace({ command: 'str_replace', path: '/memories/omit.md', old_str: 'DELETEME' } as any);
console.log(JSON.stringify(await fs.readFile(path.join(base, 'memories/omit.md'), 'utf8')));
// "keep undefined keep\n"
```
### Expected
- `old_str` is matched against the whole file content, as the Python SDK does: `'line1\nline2'` is found once and replaced, leaving `joined\nline3\n`.
- A missing `new_str` behaves as `''`, leaving `keep keep\n`.
### Suggested fix
Search the full content with `indexOf` (collecting the 1-based line number of each hit for the "Multiple occurrences ... in lines:" message) and default `new_str` to `''`. The open PR #1178 fixes the `$&` replacement-pattern expansion in the same `content.replace(...)` statement; building the new content by string concatenation would cover all three at once.
Guia de contribuição
Direção de pesquisa
Start with src/tools/memory/node.ts and the BetaLocalFilesystemMemoryTool.str_replace entry point. Reproduce the multi-line replacement and omitted new_str cases from the issue, then verify that whole-file matching works and omission deletes old_str without writing undefined; the expected file contents provide the completion check.
Escrita pelo modelo de indexação a partir do texto da issue.
Avaliação
- Stack de tecnologia
- node.js, typescript
- Domínio
- tooling
- Tipo de issue
- Bug
- Dificuldade
- 2/5
- Tempo estimado
- 1-3 horas
- Status de atividade
- Ativa
- Clareza
- Claramente especificada
- Facilidade para iniciantes
- 85/100