{"record":{"id":"7d4f3f3f9354b01d","repo":"JuliusBrussee/caveman","slug":"caveman-code-old-string-appears-occurrences-ti","errorCode":null,"errorMessage":"caveman-code: old_string appears ${occurrences} times in ${input.path}; add surrounding context or pass replace_all","messagePattern":"caveman-code: old_string appears (.+?) times in (.+?); add surrounding context or pass replace_all","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/agent/src/code.ts","lineNumber":352,"sourceCode":"      old_string: schema.string(),\n      new_string: schema.string(),\n      replace_all: schema.optional(schema.boolean()),\n    }),\n    effect: \"write\",\n    result: \"inline\",\n    timeoutMs: READ_TIMEOUT_MS,\n    async execute(input) {\n      if (input.old_string === input.new_string) {\n        throw new Error(\"caveman-code: old_string and new_string are identical\");\n      }\n      const target = await contained(input.path);\n      const content = await readFile(target, \"utf8\");\n      const occurrences = content.split(input.old_string).length - 1;\n      if (occurrences === 0) {\n        throw new Error(`caveman-code: old_string not found in ${input.path}`);\n      }\n      if (occurrences > 1 && input.replace_all !== true) {\n        throw new Error(\n          `caveman-code: old_string appears ${occurrences} times in ${input.path}; ` +\n          \"add surrounding context or pass replace_all\",\n        );\n      }\n      // split/join UNCONDITIONALLY. String.prototype.replace\n      // interprets `$&`, `$\\``, `$'`, `$$`, `$1`… in the REPLACEMENT even for a\n      // string pattern, so a new_string containing any of them would silently\n      // corrupt the file. The non-replace_all branch is guaranteed exactly one\n      // occurrence above, so joining replaces precisely that one.\n      const updated = content.split(input.old_string).join(input.new_string);\n      await writeFile(target, updated, \"utf8\");\n      const replaced = input.replace_all === true ? occurrences : 1;\n      return capOutput(\n        `edited ${input.path}: ${replaced} replacement${replaced === 1 ? \"\" : \"s\"}`,\n        caps.edit_file,\n      );\n    },\n  });","sourceCodeStart":334,"sourceCodeEnd":370,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/agent/src/code.ts#L334-L370","documentation":"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.","triggerScenarios":"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.","commonSituations":"Anchoring on a common one-liner; editing duplicated config blocks; model-generated edits that pick the function signature line without its body.","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"],"exampleFix":"// before: ambiguous anchor\nawait edit_file({ path, old_string: \"return null;\", new_string: \"return undefined;\" });\n\n// after: unique context\nawait edit_file({\n  path,\n  old_string: \"function parse(raw) {\\n  if (!raw) return null;\",\n  new_string: \"function parse(raw) {\\n  if (!raw) return undefined;\",\n});\n// or all occurrences at once:\nawait edit_file({ path, old_string: \"return null;\", new_string: \"return undefined;\", replace_all: true });","handlingStrategy":"validation","validationCode":"const occurrences = content.split(old_string).length - 1;\nif (occurrences === 0) throw new Error(\"anchor not present\");\nif (occurrences > 1 && !replace_all) {\n  throw new Error(`anchor is ambiguous (${occurrences} hits); add context or set replace_all`);\n}","typeGuard":null,"tryCatchPattern":"try {\n  await editTool.execute(input);\n} catch (err) {\n  if (err instanceof Error && /appears \\d+ times/.test(err.message)) {\n    const n = Number(/appears (\\d+) times/.exec(err.message)![1]);\n    // widen the anchor with surrounding context and retry, or escalate replace_all\n    await editTool.execute({ ...input, old_string: widen(input.old_string) });\n  } else throw err;\n}","preventionTips":["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"],"tags":["edit","tool-input","ambiguity","unique-anchor"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}