can1357/oh-my-pi · error · ToolError
Conflict #${entry.id} has no base section (2-way merge). `@b
Error message
Conflict #${entry.id} has no base section (2-way merge). `@base` is only valid for diff3 conflicts. What it means
expandContentTokens throws when the @base token is used in replacement content for a conflict that has no base section. Two-way (non-diff3) merges record only ours/theirs sides; @base only exists for diff3-style conflicts, so expanding @base to nothing would silently corrupt the resolution and the tool refuses instead.
Source
Thrown at packages/coding-agent/src/tools/conflict-detect.ts:568
* - `@base` → expands to `baseLines`; throws if no base section was
* recorded (i.e. the conflict was 2-way, not diff3).
* - `@both` → expands to `oursLines` then `theirsLines`.
*/
export function expandContentTokens(content: string, entry: ConflictEntry): string {
const inputLines = content.split("\n");
const out: string[] = [];
for (const rawLine of inputLines) {
const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
switch (line) {
case "@ours":
out.push(...entry.oursLines);
break;
case "@theirs":
out.push(...entry.theirsLines);
break;
case "@base":
if (!entry.baseLines) {
throw new ToolError(
`Conflict #${entry.id} has no base section (2-way merge). \`@base\` is only valid for diff3 conflicts.`,
);
}
out.push(...entry.baseLines);
break;
case "@both":
out.push(...entry.oursLines, ...entry.theirsLines);
break;
default:
out.push(rawLine);
break;
}
}
return out.join("\n");
}
/** Reconstruct a conflict-marker line from prefix and optional label. */
function markerLine(prefix: string, label: string | undefined): string {View on GitHub (pinned to 9690622007)
Solutions
- Remove the @base token and resolve using @ours/@theirs/@both, or paste the intended base content explicitly.
- Re-register conflicts with diff3 style (git config merge.conflictStyle diff3, re-trigger the merge) if a true base is needed.
- Use the conflict's ours/theirs sides as a stand-in for base when the ancestor version isn't available.
Example fix
// before
write({ path: 'conflict://2', content: '@base' }); // 2-way conflict
// after
write({ path: 'conflict://2', content: '@theirs' }); // no base side exists Defensive patterns
Strategy: validation
Validate before calling
if (content.includes('@base') && entry.baseLines === undefined) {
throw new Error(`conflict #${entry.id} is 2-way; @base unavailable`);
} Type guard
function hasBase(entry) {
return entry.baseLines !== undefined;
} Try / catch
try {
await write({ path: `conflict://${id}`, content });
} catch (err) {
if (String(err?.message).includes('has no base section')) {
await write({ path: `conflict://${id}`, content: content.replaceAll('@base', '@ours') });
} else throw err;
} Prevention
- Detect conflict style (baseLines presence) before using @base in templates.
- Don't assume diff3; repos often run with plain merge style.
- Keep @base only in templates gated on diff3 availability.
- For bulk wildcard writes, use @base-free content unless all conflicts are diff3.
When it happens
Trigger: Writing content containing '@base' (e.g. via conflict://<id> or conflict://*) against a conflict registered from a 2-way merge (merge.conflictStyle diff, not diff3/zdiff3); a generic @ours/@theirs/@base template applied to all conflicts where some are 2-way.
Common situations: Repo git config switched from diff3 to plain merge style (or was never diff3) while tooling assumes diff3; bulk wildcard writes with @base hitting a mix of conflict styles; agents copying resolution templates that assume diff3.
Related errors
- Conflict #${entry.id} has no base section (2-way merge). 'co
- Invalid conflict URI '${raw}': wildcard 'conflict://*' does
- Invalid conflict URI '${raw}': must be 'conflict://<N>', 'co
- Invalid conflict URI '${raw}': scope must be one of 'ours',
- Conflict #${entry.id} no longer present in '${entry.displayP
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/952a065077d32103.
Report an issue: GitHub.