can1357/oh-my-pi · error · ToolError

Conflict #${entry.id} has no base section (2-way merge). 'co

Error message

Conflict #${entry.id} has no base section (2-way merge). 'conflict://${entry.id}/base' is only valid for diff3 conflicts.

What it means

The scope reader throws when reading conflict://<id>/base for a conflict that has no base section. baseLines/baseLine are only populated for diff3-style conflicts (which record the common ancestor between the ======= and >>>>>>> markers); for plain 2-way conflicts that section doesn't exist, so requesting it throws instead of returning empty content.

Source

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

 *   original file positions.
 *
 * Bare (no scope) returns the full block including marker lines. A
 * scoped view returns only that side's body — `base` throws when the
 * recorded conflict is a 2-way merge with no base section.
 */
export function renderConflictRegion(
	entry: ConflictEntry,
	scope: ConflictScope | undefined,
): { lines: string[]; startLine: number } {
	if (scope === "ours") {
		return { lines: [...entry.oursLines], startLine: entry.startLine + 1 };
	}
	if (scope === "theirs") {
		return { lines: [...entry.theirsLines], startLine: entry.separatorLine + 1 };
	}
	if (scope === "base") {
		if (entry.baseLines === undefined || entry.baseLine === undefined) {
			throw new ToolError(
				`Conflict #${entry.id} has no base section (2-way merge). 'conflict://${entry.id}/base' is only valid for diff3 conflicts.`,
			);
		}
		return { lines: [...entry.baseLines], startLine: entry.baseLine + 1 };
	}
	const out: string[] = [];
	out.push(markerLine("<<<<<<<", entry.oursLabel));
	out.push(...entry.oursLines);
	if (entry.baseLines !== undefined) {
		out.push(markerLine("|||||||", entry.baseLabel));
		out.push(...entry.baseLines);
	}
	out.push("=======");
	out.push(...entry.theirsLines);
	out.push(markerLine(">>>>>>>", entry.theirsLabel));
	return { lines: out, startLine: entry.startLine };
}

View on GitHub (pinned to 9690622007)

Solutions

  1. Read the conflict without a scope (conflict://5) or use /ours and /theirs, which exist for both styles.
  2. Enable diff3 style (git config merge.conflictStyle diff3) and re-trigger the merge to get a base section, then re-read.
  3. Check the conflict entry's baseLines (or conflict style) before requesting the base scope in scripts.

Example fix

// before
const base = await read({ path: 'conflict://5/base' }); // 2-way conflict
// after
const whole = await read({ path: 'conflict://5' }); // ours+theirs, safe for both styles
Defensive patterns

Strategy: type-guard

Validate before calling

if (entry.baseLines !== undefined) {
  const base = await read({ path: `conflict://${entry.id}/base` });
} else {
  const whole = await read({ path: `conflict://${entry.id}` });
}

Type guard

function supportsBaseScope(entry) {
  return entry.baseLines !== undefined && entry.baseLine !== undefined;
}

Try / catch

try {
  const base = await read({ path: `conflict://${id}/base` });
} catch (err) {
  if (String(err?.message).includes('has no base section')) {
    const whole = await read({ path: `conflict://${id}` }); // 2-way fallback
  } else throw err;
}

Prevention

When it happens

Trigger: Reading 'conflict://5/base' where conflict 5 came from a 2-way merge; iterating all scopes (ours/theirs/base) for every registered conflict without checking which style produced them; an agent assuming diff3 output on a repo configured with merge.conflictStyle=diff.

Common situations: Repos without git config merge.conflictStyle=diff3/zdiff3; conflicts imported from tools (rebase, cherry-pick) that emit 2-way markers; generic tooling that blindly requests every scope.

Related errors


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