JuliusBrussee/caveman · error · Error
caveman-code: old_string appears ${occurrences} times in ${i
Error message
caveman-code: old_string appears ${occurrences} times in ${input.path}; add surrounding context or pass replace_all What it means
edit_file found more than one occurrence of old_string and replace_all was not set. Without a unique anchor, replacing 'the first match' would be nondeterministic from the caller's perspective, so the tool refuses and asks for more context or an explicit replace_all.
Source
Thrown at packages/agent/src/code.ts:352
old_string: schema.string(),
new_string: schema.string(),
replace_all: schema.optional(schema.boolean()),
}),
effect: "write",
result: "inline",
timeoutMs: READ_TIMEOUT_MS,
async execute(input) {
if (input.old_string === input.new_string) {
throw new Error("caveman-code: old_string and new_string are identical");
}
const target = await contained(input.path);
const content = await readFile(target, "utf8");
const occurrences = content.split(input.old_string).length - 1;
if (occurrences === 0) {
throw new Error(`caveman-code: old_string not found in ${input.path}`);
}
if (occurrences > 1 && input.replace_all !== true) {
throw new Error(
`caveman-code: old_string appears ${occurrences} times in ${input.path}; ` +
"add surrounding context or pass replace_all",
);
}
// split/join UNCONDITIONALLY. String.prototype.replace
// interprets `$&`, `$\``, `$'`, `$$`, `$1`… in the REPLACEMENT even for a
// string pattern, so a new_string containing any of them would silently
// corrupt the file. The non-replace_all branch is guaranteed exactly one
// occurrence above, so joining replaces precisely that one.
const updated = content.split(input.old_string).join(input.new_string);
await writeFile(target, updated, "utf8");
const replaced = input.replace_all === true ? occurrences : 1;
return capOutput(
`edited ${input.path}: ${replaced} replacement${replaced === 1 ? "" : "s"}`,
caps.edit_file,
);
},
});View on GitHub (pinned to 27d5a3981a)
Solutions
- Expand old_string with surrounding lines (e.g. include the preceding line or the full block) until it is unique in the file
- If you genuinely want every occurrence replaced, pass replace_all: true
- Use the reported occurrence count as feedback: it tells you exactly how much more context you need
Example fix
// before: ambiguous anchor
await edit_file({ path, old_string: "return null;", new_string: "return undefined;" });
// after: unique context
await edit_file({
path,
old_string: "function parse(raw) {\n if (!raw) return null;",
new_string: "function parse(raw) {\n if (!raw) return undefined;",
});
// or all occurrences at once:
await edit_file({ path, old_string: "return null;", new_string: "return undefined;", replace_all: true }); Defensive patterns
Strategy: validation
Validate before calling
const occurrences = content.split(old_string).length - 1;
if (occurrences === 0) throw new Error("anchor not present");
if (occurrences > 1 && !replace_all) {
throw new Error(`anchor is ambiguous (${occurrences} hits); add context or set replace_all`);
} Try / catch
try {
await editTool.execute(input);
} catch (err) {
if (err instanceof Error && /appears \d+ times/.test(err.message)) {
const n = Number(/appears (\d+) times/.exec(err.message)![1]);
// widen the anchor with surrounding context and retry, or escalate replace_all
await editTool.execute({ ...input, old_string: widen(input.old_string) });
} else throw err;
} Prevention
- Include at least one full line plus a neighbor line in every anchor
- Grep the file for your anchor first; if you get multiple hits, extend it before calling edit_file
When it happens
Trigger: Passing a short or generic anchor (e.g. 'return null;' or '}' ) that occurs multiple times; editing repeated boilerplate (imports, license headers) without replace_all: true.
Common situations: Anchoring on a common one-liner; editing duplicated config blocks; model-generated edits that pick the function signature line without its body.
Related errors
- caveman-code: old_string and new_string are identical
- caveman-code: old_string not found in ${input.path}
- caveman-code: multiple provider credentials found; set CAVE_
- cave_agent_definition_invalid
- multiple provider credentials detected; pass --provider
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/7d4f3f3f9354b01d.
Report an issue: GitHub.