{"record":{"id":"f318d5cf1048122c","repo":"JuliusBrussee/caveman","slug":"aider-config-has-duplicate-read-keys-refusing-uns","errorCode":null,"errorMessage":"Aider config has duplicate read keys; refusing unsafe merge","messagePattern":"Aider config has duplicate read keys; refusing unsafe merge","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/cli/src/index.ts","lineNumber":6397,"sourceCode":"function aiderNativeConfig(source: string, route: string, corePath: string): { text: string; routeBlock: string; previousRouteLine: string | null; readBlock: string } {\n  if ([AIDER_NATIVE_ROUTE_BEGIN, AIDER_NATIVE_ROUTE_END, AIDER_NATIVE_READ_BEGIN, AIDER_NATIVE_READ_END].some((marker) => source.includes(marker))) {\n    throw new Error(\"existing Aider Caveman block is unjournaled; run `caveman doctor aider`\");\n  }\n  const newline = source.includes(\"\\r\\n\") ? \"\\r\\n\" : \"\\n\";\n  const lines = source.replace(/\\r\\n/g, \"\\n\").split(\"\\n\");\n  const routeIndexes = lines.flatMap((line, index) => /^openai-api-base\\s*:/.test(line) ? [index] : []);\n  if (routeIndexes.length > 1) throw new Error(\"Aider config has duplicate openai-api-base keys; refusing unsafe merge\");\n  const routeBlockLines = [AIDER_NATIVE_ROUTE_BEGIN, `openai-api-base: ${yamlQuote(route)}`, AIDER_NATIVE_ROUTE_END];\n  const routeBlock = routeBlockLines.join(newline);\n  const previousRouteLine = routeIndexes.length === 1 ? lines[routeIndexes[0]!]! : null;\n  if (routeIndexes.length === 1) lines.splice(routeIndexes[0]!, 1, ...routeBlockLines);\n  else {\n    if (lines.some((line) => line.trim() !== \"\") && lines[lines.length - 1]?.trim()) lines.push(\"\");\n    lines.push(...routeBlockLines);\n  }\n\n  const readIndexes = lines.flatMap((line, index) => /^read\\s*:/.test(line) ? [index] : []);\n  if (readIndexes.length > 1) throw new Error(\"Aider config has duplicate read keys; refusing unsafe merge\");\n  let readBlockLines: string[];\n  if (readIndexes.length === 1) {\n    const readIndex = readIndexes[0]!;\n    if (!/^read\\s*:\\s*(?:#.*)?$/.test(lines[readIndex]!)) {\n      throw new Error(\"Aider config uses inline/scalar read; use block-list form before enabling Caveman\");\n    }\n    let end = readIndex + 1;\n    while (end < lines.length && !/^[A-Za-z0-9][A-Za-z0-9_-]*\\s*:/.test(lines[end]!)) end++;\n    readBlockLines = [`  ${AIDER_NATIVE_READ_BEGIN}`, `  - ${yamlQuote(corePath)}`, `  ${AIDER_NATIVE_READ_END}`];\n    lines.splice(end, 0, ...readBlockLines);\n  } else {\n    if (lines.some((line) => line.trim() !== \"\") && lines[lines.length - 1]?.trim()) lines.push(\"\");\n    readBlockLines = [AIDER_NATIVE_READ_BEGIN, \"read:\", `  - ${yamlQuote(corePath)}`, AIDER_NATIVE_READ_END];\n    lines.push(...readBlockLines);\n  }\n  const readBlock = readBlockLines.join(newline);\n  const text = lines.join(newline).replace(/(?:\\r?\\n)+$/, \"\") + newline;\n  return { text, routeBlock, previousRouteLine, readBlock };","sourceCodeStart":6379,"sourceCodeEnd":6415,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/packages/cli/src/index.ts#L6379-L6415","documentation":"aiderNativeConfig() appends the Caveman core markdown path into Aider's `read:` list in ~/.aider.conf.yml. If the file declares two or more `read:` keys, which list Aider actually uses is ambiguous, so the tool refuses an unsafe merge.","triggerScenarios":"Native Aider install when ~/.aider.conf.yml contains two or more lines matching /^read\\s*:/ at top level.","commonSituations":"Copy-pasted config snippets adding a second read: block; merge conflicts in dotfile git repos; hand edits that appended instead of merged.","solutions":["Merge the contents of the duplicate read: lists into a single read: block-list","Delete the redundant key, keeping one read: entry","Re-run the caveman aider install"],"exampleFix":"# before\nread:\n  - ./CONVENTIONS.md\nread:\n  - ./OTHER.md\n\n# after\nread:\n  - ./CONVENTIONS.md\n  - ./OTHER.md","handlingStrategy":"validation","validationCode":"import { readFileSync } from \"node:fs\";\nfunction aiderNoDuplicateRead(path: string): boolean {\n  try { return readFileSync(path, \"utf8\").split(/\\r?\\n/).filter((l) => /^read\\s*:/.test(l)).length <= 1; }\n  catch { return true; }\n}","typeGuard":null,"tryCatchPattern":"try { nativeInstallAider(); } catch (e) {\n  if (e instanceof Error && /duplicate read keys/.test(e.message)) {\n    mergeAiderReadLists(); nativeInstallAider();\n  } else throw e;\n}","preventionTips":["Maintain a single read: block-list in .aider.conf.yml","Merge lists when combining config snippets instead of adding a second key","Run yamllint on aider config before integrations touch it"],"tags":["aider","yaml","duplicate-keys","merge-safety"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}