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

  1. Check oldText is non-empty before calling replaceText and skip/short-circuit the edit if it is.
  2. Fix the upstream source producing the empty string (empty capture group, failed read, empty prompt field).
  3. 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

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


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/040b4e5b6fc5bb20. Report an issue: GitHub.