can1357/oh-my-pi · error

Found ${occurrences} occurrences${pathSuffix}${moreMsg}:\n\n

Error message

Found ${occurrences} occurrences${pathSuffix}${moreMsg}:\n\n${previews}\n\nAdd more context lines to disambiguate.

What it means

When findMatch() locates more than one occurrence of oldText in the (LF-normalized) content, replaceText() refuses to guess which one to replace and throws a formatted error listing the occurrence count, per-occurrence previews, and the file path. The library requires unambiguous matches; the caller must add more surrounding context lines to make oldText unique.

Source

Thrown at packages/coding-agent/src/edit/diff.ts:917

		replacements.sort((a, b) => a.startIndex - b.startIndex);
		const parts: string[] = [];
		let sourceIndex = 0;
		for (const replacement of replacements) {
			parts.push(normalizedContent.substring(sourceIndex, replacement.startIndex), replacement.text);
			sourceIndex = replacement.endIndex;
		}
		parts.push(normalizedContent.substring(sourceIndex));
		return { content: parts.join(""), count: replacements.length };
	}

	// Single replacement mode
	const matchOutcome = findMatch(normalizedContent, normalizedOldText, {
		allowFuzzy: options.fuzzy,
		threshold,
	});

	if (matchOutcome.occurrences && matchOutcome.occurrences > 1) {
		throw new Error(formatOccurrenceMatchError(matchOutcome.occurrences, matchOutcome.occurrencePreviews));
	}

	if (!matchOutcome.match) {
		return { content: normalizedContent, count: 0 };
	}

	const match = matchOutcome.match;
	const adjustedNewText = adjustIndentation(normalizedOldText, match.actualText, normalizedNewText);
	normalizedContent =
		normalizedContent.substring(0, match.startIndex) +
		adjustedNewText +
		normalizedContent.substring(match.startIndex + match.actualText.length);

	return { content: normalizedContent, count: 1 };
}

// ═══════════════════════════════════════════════════════════════════════════
// Preview/Diff Computation

View on GitHub (pinned to 9690622007)

Solutions

  1. Expand oldText with more surrounding lines (unique imports, distinctive comments, neighboring identifiers) until it matches exactly one location.
  2. Include line-number/hashline anchors via the hashline edit format instead of a plain text match.
  3. If replacing ALL occurrences is intended, loop with a replace-all API rather than single replaceText.

Example fix

// before
replaceText(content, 'return null;', 'return undefined;', { fuzzy: false });
// after
replaceText(
  content,
  'function findUser(id: string) {\n  return null;\n}',
  'function findUser(id: string) {\n  return undefined;\n}',
  { fuzzy: false }
);
Defensive patterns

Strategy: validation

Validate before calling

const occurrences = content.split(oldText).length - 1;
if (occurrences !== 1) {
  throw new Error(`oldText matches ${occurrences} times; add context to disambiguate`);
}
const result = replaceText(content, oldText, newText, options);

Try / catch

try {
  return replaceText(content, oldText, newText, { fuzzy: false });
} catch (e) {
  if (e instanceof Error && e.message.includes('occurrences')) {
    // retry with expanded context derived from occurrence previews in e.message
    return replaceTextWithMoreContext(content, oldText, newText);
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling replaceText (or the edit tool backed by it) with oldText that appears 2+ times in the target file, e.g. a short snippet, a repeated import line, or a duplicated function stub; also when options.fuzzy lets near-identical regions both pass the threshold.

Common situations: Editing repeated YAML/JSON keys, duplicated boilerplate (empty catch blocks, repeated log lines), or agents editing files with copy-pasted sections like two identical test cases or config blocks.

Related errors


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