{"record":{"id":"040b4e5b6fc5bb20","repo":"can1357/oh-my-pi","slug":"oldtext-must-not-be-empty","errorCode":null,"errorMessage":"oldText must not be empty.","messagePattern":"oldText must not be empty\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/edit/diff.ts","lineNumber":852,"sourceCode":"\n\t\tif (trimmed.startsWith(\"@@\") && lines.slice(i + 1).every(next => next.trim() === \"\")) {\n\t\t\tbreak;\n\t\t}\n\n\t\tconst { hunk, linesConsumed } = parseOneHunk(lines.slice(i), i + 1, true);\n\t\thunks.push(hunk);\n\t\ti += linesConsumed;\n\t}\n\n\treturn hunks;\n}\n\n/**\n * Find and replace text in content using fuzzy matching.\n */\nexport function replaceText(content: string, oldText: string, newText: string, options: ReplaceOptions): ReplaceResult {\n\tif (oldText.length === 0) {\n\t\tthrow new Error(\"oldText must not be empty.\");\n\t}\n\tconst threshold = options.threshold ?? DEFAULT_FUZZY_THRESHOLD;\n\tlet normalizedContent = normalizeToLF(content);\n\tconst normalizedOldText = normalizeToLF(oldText);\n\tconst normalizedNewText = normalizeToLF(newText);\n\n\tif (options.all) {\n\t\t// Check for exact matches first\n\t\tconst exactCount = normalizedContent.split(normalizedOldText).length - 1;\n\t\tif (exactCount > 0) {\n\t\t\treturn {\n\t\t\t\tcontent: normalizedContent.split(normalizedOldText).join(normalizedNewText),\n\t\t\t\tcount: exactCount,\n\t\t\t};\n\t\t}\n\n\t\t// Match against the immutable source so inserted replacement text cannot become a later candidate.\n\t\tconst replacements: Array<{ startIndex: number; endIndex: number; text: string }> = [];","sourceCodeStart":834,"sourceCodeEnd":870,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/edit/diff.ts#L834-L870","documentation":"replaceText() in the edit fuzzy-diff module throws this before doing any work when the caller passes an empty oldText string. An empty needle would match at position 0 of any file (or be meaningless for replacement), so the library rejects it eagerly instead of producing a bogus no-op or whole-file rewrite. It is a plain Error, thrown synchronously from the public replaceText entry point.","triggerScenarios":"Calling replaceText(content, '', newText, options), or building oldText from a variable that resolved to '' (empty regex capture, empty file read, default-initialized variable).","commonSituations":"Template-based editors where the search text is interpolated from config or LLM output that came back empty; scripted migrations reading an empty anchor file; agents emitting a replace call with an empty search block.","solutions":["Check oldText is non-empty before calling replaceText and skip/short-circuit the edit if it is.","Fix the upstream source producing the empty string (empty capture group, failed read, empty prompt field).","If the intent is 'insert', use an insert/edit API rather than a replace with empty oldText."],"exampleFix":"// before\nconst res = replaceText(content, match[1] ?? '', replacement, { fuzzy: true });\n// after\nif (!match[1]) throw new Error(`No search text captured for replacement (offset ${match.index}).`);\nconst res = replaceText(content, match[1], replacement, { fuzzy: true });","handlingStrategy":"validation","validationCode":"if (typeof oldText !== 'string' || oldText.length === 0) {\n  throw new Error('replaceText requires non-empty oldText');\n}\nconst result = replaceText(content, oldText, newText, options);","typeGuard":"function hasSearchText(t: unknown): t is string {\n  return typeof t === 'string' && t.length > 0;\n}","tryCatchPattern":"try {\n  return replaceText(content, oldText, newText, options);\n} catch (e) {\n  if (e instanceof Error && e.message === 'oldText must not be empty.') {\n    return { content, count: 0 }; // skip empty-search edits\n  }\n  throw e;\n}","preventionTips":["Guard interpolated search strings from templates/LLM output before editing.","Use an explicit insert API for insertion instead of empty oldText.","Assert captured groups (match[1]) are non-empty before using them as search text."],"tags":["validation","argument-error","edit-tool"],"backgroundTag":"empty-required-argument","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}