can1357/oh-my-pi · error
oldText must not be empty.
Error message
oldText must not be empty.
What it means
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.
Source
Thrown at packages/coding-agent/src/edit/diff.ts:852
if (trimmed.startsWith("@@") && lines.slice(i + 1).every(next => next.trim() === "")) {
break;
}
const { hunk, linesConsumed } = parseOneHunk(lines.slice(i), i + 1, true);
hunks.push(hunk);
i += linesConsumed;
}
return hunks;
}
/**
* Find and replace text in content using fuzzy matching.
*/
export function replaceText(content: string, oldText: string, newText: string, options: ReplaceOptions): ReplaceResult {
if (oldText.length === 0) {
throw new Error("oldText must not be empty.");
}
const threshold = options.threshold ?? DEFAULT_FUZZY_THRESHOLD;
let normalizedContent = normalizeToLF(content);
const normalizedOldText = normalizeToLF(oldText);
const normalizedNewText = normalizeToLF(newText);
if (options.all) {
// Check for exact matches first
const exactCount = normalizedContent.split(normalizedOldText).length - 1;
if (exactCount > 0) {
return {
content: normalizedContent.split(normalizedOldText).join(normalizedNewText),
count: exactCount,
};
}
// Match against the immutable source so inserted replacement text cannot become a later candidate.
const replacements: Array<{ startIndex: number; endIndex: number; text: string }> = [];View on GitHub (pinned to 9690622007)
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.
Example fix
// before
const res = replaceText(content, match[1] ?? '', replacement, { fuzzy: true });
// after
if (!match[1]) throw new Error(`No search text captured for replacement (offset ${match.index}).`);
const res = replaceText(content, match[1], replacement, { fuzzy: true }); Defensive patterns
Strategy: validation
Validate before calling
if (typeof oldText !== 'string' || oldText.length === 0) {
throw new Error('replaceText requires non-empty oldText');
}
const result = replaceText(content, oldText, newText, options); Type guard
function hasSearchText(t: unknown): t is string {
return typeof t === 'string' && t.length > 0;
} Try / catch
try {
return replaceText(content, oldText, newText, options);
} catch (e) {
if (e instanceof Error && e.message === 'oldText must not be empty.') {
return { content, count: 0 }; // skip empty-search edits
}
throw e;
} Prevention
- 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.
When it happens
Trigger: Calling replaceText(content, '', newText, options), or building oldText from a variable that resolved to '' (empty regex capture, empty file read, default-initialized variable).
Common situations: 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.
Related errors
- Found ${occurrences} occurrences${pathSuffix}${moreMsg}:\n\n
- Missing hashline snapshot tag for ${sectionPath}; use `${HL_
- Multiple hashline sections resolve to the same file (${previ
- At least one output ID is required
- Unrecognized source format. Did you mean './${source}' (loca
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/040b4e5b6fc5bb20.
Report an issue: GitHub.