can1357/oh-my-pi · error · ToolError

Invalid conflict URI '${raw}': must be 'conflict://<N>', 'co

Error message

Invalid conflict URI '${raw}': must be 'conflict://<N>', 'conflict://<N>/<scope>', or 'conflict://*' where N is a positive integer surfaced by a prior `read`.

What it means

parseConflictUri throws this when the conflict id segment is not a positive integer (and not the '*' wildcard). Valid forms are conflict://<N>, conflict://<N>/<scope>, or conflict://*; anything else with the conflict:// scheme — 'conflict://abc', 'conflict://latest', 'conflict://3.2' — is rejected early with this message rather than failing later with a confusing not-found.

Source

Thrown at packages/coding-agent/src/tools/conflict-detect.ts:300

	const match = raw.match(CONFLICT_URI_RE);
	if (!match) return null;
	const recoveredPrefix = match[1];
	const tail = match[2];
	const slashIdx = tail.indexOf("/");
	const idPart = slashIdx === -1 ? tail : tail.slice(0, slashIdx);
	const scopePart = slashIdx === -1 ? undefined : tail.slice(slashIdx + 1);

	if (idPart === "*") {
		if (scopePart !== undefined) {
			throw new ToolError(
				`Invalid conflict URI '${raw}': wildcard 'conflict://*' does not accept a scope segment. Drop '/${scopePart}' or use a numeric id.`,
			);
		}
		return recoveredPrefix !== undefined ? { id: "*", recoveredPrefix } : { id: "*" };
	}

	if (!/^\d+$/.test(idPart)) {
		throw new ToolError(
			`Invalid conflict URI '${raw}': must be 'conflict://<N>', 'conflict://<N>/<scope>', or 'conflict://*' where N is a positive integer surfaced by a prior \`read\`.`,
		);
	}
	const id = Number.parseInt(idPart, 10);
	if (!Number.isFinite(id) || id < 1) {
		throw new ToolError(`Invalid conflict URI '${raw}': id must be ≥ 1.`);
	}

	let scope: ConflictScope | undefined;
	if (scopePart !== undefined) {
		if (!CONFLICT_SCOPES.has(scopePart as ConflictScope)) {
			throw new ToolError(
				`Invalid conflict URI '${raw}': scope must be one of 'ours', 'theirs', 'base', or omitted (e.g. 'conflict://${id}/theirs').`,
			);
		}
		scope = scopePart as ConflictScope;
	}

View on GitHub (pinned to 9690622007)

Solutions

  1. Use the numeric id surfaced by a prior read of the file (conflict ids are assigned when conflicts are registered).
  2. Read the conflicted file first to register conflicts and obtain valid numeric ids, then reference them.
  3. Fix id interpolation in scripts/templates so a real number or '*' lands in the URI.

Example fix

// before
write({ path: 'conflict://latest', content });
// after
const id = conflicts[0].id; // from a prior read
write({ path: `conflict://${id}`, content });
Defensive patterns

Strategy: validation

Validate before calling

if (!/^(?:[^:]+:)?conflict:\/\/(\d+|\*)(?:\/(?:ours|theirs|base))?$/.test(path)) {
  throw new Error(`not a valid conflict URI: ${path}`);
}

Type guard

function isConflictUri(s) {
  return /^(?:[^:]+:)?conflict:\/\/(\d+|\*)(?:\/(?:ours|theirs|base))?$/.test(s);
}

Try / catch

try {
  parseConflictUri(raw);
} catch (err) {
  if (String(err?.message).includes("must be 'conflict://<N>'")) {
    // recover: re-read the file to get numeric ids
  } else throw err;
}

Prevention

When it happens

Trigger: Passing a non-numeric id like 'conflict://latest' or 'conflict://head'; including units or formatting ('conflict://3rd', 'conflict://#3'); an id placeholder left unsubstituted in a template.

Common situations: Agents hallucinating descriptive ids ('latest', 'first'); scripts interpolating undefined variables into the URI; ids copied from docs without replacing N.

Related errors


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