windmill-labs/windmill · error

old_string matched ${matchCount} locations. Make it more spe

Error message

old_string matched ${matchCount} locations. Make it more specific or set replace_all to true.

What it means

findAndReplace requires old_string to be unique unless replaceAll is set. When the literal old_string occurs more than once and replaceAll is false, it throws this error with the exact match count so the caller (usually the model) can disambiguate. This guards against unintended edits at multiple locations.

Source

Thrown at frontend/src/lib/components/copilot/chat/shared.ts:311

/**
 * Match-count-validated exact text replacement. Throws when `oldString` is
 * missing, and (unless `replaceAll`) when it appears more than once.
 * `contextLabel` flows into the error message ("not found in the <label>.").
 */
export function findAndReplace(
	content: string,
	oldString: string,
	newString: string,
	replaceAll: boolean,
	contextLabel: string
): string {
	const matchCount = countExactMatches(content, oldString)
	if (matchCount === 0) {
		throw new Error(`old_string was not found in the ${contextLabel}.`)
	}
	if (!replaceAll && matchCount !== 1) {
		throw new Error(
			`old_string matched ${matchCount} locations. Make it more specific or set replace_all to true.`
		)
	}
	return applyExactReplace(content, oldString, newString, replaceAll)
}

export const extractAllModules = (modules: FlowModule[]): FlowModule[] => {
	return modules.flatMap((m) => {
		if (m.value.type === 'forloopflow' || m.value.type === 'whileloopflow') {
			return [m, ...extractAllModules(m.value.modules)]
		}
		if (m.value.type === 'branchall') {
			return [m, ...extractAllModules(m.value.branches.flatMap((b) => b.modules))]
		}
		if (m.value.type === 'branchone') {
			return [
				m,
				...extractAllModules([...m.value.branches.flatMap((b) => b.modules), ...m.value.default])

View on GitHub (pinned to e474e8803c)

Solutions

  1. Include more surrounding lines in old_string so it matches exactly one location, then retry.
  2. If all occurrences should change, set replace_all: true on the tool call.
  3. Replace a larger unique block that contains the target line as one edit.
  4. Split the edit into multiple calls, each with context that makes the target occurrence unique.

Example fix

// before
findAndReplace(content, "return null", "return undefined", false, "the file") // 3 matches
// after
findAndReplace(content, "if (!user) {\n\treturn null\n}", "if (!user) {\n\treturn undefined\n}", false, "the file")
Defensive patterns

Strategy: validation

Validate before calling

const count = content.split(oldString).length - 1
if (!replaceAll && count > 1) {
  throw new Error(`old_string matches ${count} places; add surrounding context or set replace_all`)
}

Try / catch

try {
  return findAndReplace(content, oldString, newString, replaceAll, 'the file')
} catch (e) {
  const m = e.message.match(/matched (\d+) locations/)
  if (m) {
    const widened = widenWithSurroundingContext(content, oldString, 3)
    return findAndReplace(content, widened, replaceSnippet(newString), false, 'the file')
  }
  throw e
}

Prevention

When it happens

Trigger: An edit tool passed a short or generic old_string (e.g. '});', a repeated variable assignment, or a common import line) that matches N>1 places, with replace_all=false. countExactMatches found multiple occurrences in the file or flow JSON.

Common situations: Model tries to edit a common pattern like a closing brace, a repeated import, or 'return null' that exists in several functions; user asks to change one occurrence of a duplicated block; generated code contains two identical placeholder lines.

Related errors


AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03). Data as JSON: /api/errors/2236ad27463d0eb0. Report an issue: GitHub.