can1357/oh-my-pi · error · ToolError

Invalid conflict URI '${raw}': scope must be one of 'ours',

Error message

Invalid conflict URI '${raw}': scope must be one of 'ours', 'theirs', 'base', or omitted (e.g. 'conflict://${id}/theirs').

What it means

parseConflictUri throws this when the scope segment after the id is not one of the allowed conflict scopes: 'ours', 'theirs', or 'base' (or omitted entirely). Unknown scopes like 'local', 'remote', 'incoming', or 'merged' are rejected so a typo can't silently resolve to the wrong side of the conflict.

Source

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

			);
		}
		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;
	}

	return recoveredPrefix !== undefined ? { id, scope, recoveredPrefix } : { id, scope };
}

/** Result of {@link spliceConflict}: the new file text plus any boundary-echo repair applied. */
export interface ConflictSplice {
	text: string;
	/** Replacement lines dropped because they duplicated the context directly above the region. */
	trimmedLeading: number;
	/** Replacement lines dropped because they duplicated the context directly below the region. */
	trimmedTrailing: number;
}

View on GitHub (pinned to 9690622007)

Solutions

  1. Use exactly one of 'ours', 'theirs', or 'base' as the scope (lowercase).
  2. Map git terminology: local/current → ours, remote/incoming → theirs, ancestor/common → base.
  3. Omit the scope entirely (conflict://3) to operate on the whole conflict block with @-tokens in content.

Example fix

// before
write({ path: 'conflict://3/remote', content: '@remote' });
// after
write({ path: 'conflict://3/theirs', content: '@theirs' });
Defensive patterns

Strategy: validation

Validate before calling

const SCOPES = new Set(['ours', 'theirs', 'base']);
if (scope !== undefined && !SCOPES.has(scope)) {
  throw new Error(`scope must be ours|theirs|base, got ${scope}`);
}
const uri = scope ? `conflict://${id}/${scope}` : `conflict://${id}`;

Type guard

function isConflictScope(s) {
  return s === 'ours' || s === 'theirs' || s === 'base';
}

Try / catch

try {
  parseConflictUri(raw);
} catch (err) {
  if (String(err?.message).includes("scope must be one of 'ours', 'theirs', 'base'")) {
    // map git terms: local→ours, remote/incoming→theirs, ancestor→base
  } else throw err;
}

Prevention

When it happens

Trigger: Passing 'conflict://3/local' or 'conflict://3/remote' (git's other naming for ours/theirs); typos like 'conflict://3/Our' or 'conflict://3/there'; trailing slashes or extra path segments creating a bogus scope.

Common situations: Developers used to git terminology (local/remote, ours/theirs, current/incoming) using git words instead of the tool's scope vocabulary; case-sensitivity mistakes; agents paraphrasing scopes.

Related errors


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