can1357/oh-my-pi · error · ToolError
Malformed `conflict://*` per-id block: ${stray.length} line(
Error message
Malformed `conflict://*` per-id block: ${stray.length} line(s) are not `<id>: @side` directives (first: `${truncateDirectiveLine(sample)}`). ${tokenHint}Literal or multi-line replacement content isn't supported in a per-id block — resolve those blocks with individual `write({ path: "conflict://<N>", content })` calls (you can issue several at once). For a pure pick-a-side pass, make every non-empty line `<id>: @ours` (or @theirs/@base/@both). What it means
A `conflict://*` per-id bulk block contained lines that are not `<id>: @side` directives. Per-id blocks accept only single-line side tokens (@ours/@theirs/@base/@both); literal or multi-line replacement content must go through individual conflict://<N> writes.
Source
Thrown at packages/coding-agent/src/tools/write.ts:265
if (!match) {
stray.push(line);
continue;
}
sawDirective = true;
const id = Number.parseInt(match[1], 10);
if (map.has(id)) {
throw new ToolError(`Bulk directive lists conflict #${id} twice — each id may appear once.`);
}
map.set(id, match[2]);
}
// No directive lines at all → not a per-id block; caller uses uniform mode.
if (!sawDirective) return null;
if (stray.length > 0) {
const sample = stray[0]!;
const tokenHint = BULK_DIRECTIVE_HEAD_RE.test(sample)
? `Per-id bulk only accepts the tokens @ours/@theirs/@base/@both — one side per id, single line. `
: "";
throw new ToolError(
`Malformed \`conflict://*\` per-id block: ${stray.length} line(s) are not \`<id>: @side\` directives (first: \`${truncateDirectiveLine(sample)}\`). ` +
tokenHint +
`Literal or multi-line replacement content isn't supported in a per-id block — resolve those blocks with individual \`write({ path: "conflict://<N>", content })\` calls (you can issue several at once). ` +
`For a pure pick-a-side pass, make every non-empty line \`<id>: @ours\` (or @theirs/@base/@both).`,
);
}
return map;
}
/**
* Resolve per-id directives, preferring the pre-strip `raw` content and falling
* back to the hashline-stripped `stripped` content.
*
* Raw is preferred because the `<id>:` directive heads look exactly like
* hashline `LINE:` prefixes and would be eaten by stripping. When the two
* contents are identical (hashline mode off) a single parse decides everything,
* so a malformed-block error propagates straight through — the previous
* `?? parseBulkDirectives(...)` chain would have swallowed it and silentlyView on GitHub (pinned to 9690622007)
Solutions
- Make every non-empty line a strict '<id>: @ours' (or @theirs/@base/@both) directive
- Move literal/multi-line replacement content into individual write({ path: "conflict://<N>", content }) calls, issued in parallel
- Remove any non-directive lines from the bulk block
Example fix
// before
"1: @ours\n2: replace whole file with ..."
// after
write({ path: "conflict://*", content: "1: @ours" })
write({ path: "conflict://2", content: "replacement content" }) Defensive patterns
Strategy: validation
Validate before calling
const DIRECTIVE_RE = /^\s*\d+:\s*@(ours|theirs|base|both)\s*$/;
function isPurePerIdBlock(block: string): boolean {
return block.split("\n").filter(l => l.trim().length > 0).every(l => DIRECTIVE_RE.test(l));
}
// if false, split out literal content into individual conflict://<N> writes Try / catch
try {
await write({ path: "conflict://*", content: block });
} catch (e) {
if (e instanceof ToolError && e.message.includes("per-id block")) {
const { directives, literals } = splitBlock(block);
await write({ path: "conflict://*", content: directives });
for (const [id, text] of literals) await write({ path: `conflict://${id}`, content: text });
return;
}
throw e;
} Prevention
- Keep per-id blocks strictly single-line '<id>: @side' directives
- Route any literal replacement content through individual conflict://<N> write calls
- Validate each line against the directive regex before submitting a bulk block
When it happens
Trigger: write({ path: "conflict://*", content }) where content has directive lines plus stray lines — either arbitrary text (bad token) or lines starting like directives with unsupported payloads.
Common situations: Models trying to supply replacement file content inline in the bulk block, or pasting conflict hunks into the per-id list.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- Bulk directive lists conflict #${id} twice — each id may app
- Expected hunk to start with @@ context marker, got: '${lines
- Unexpected line in hunk: '${line}'. Lines must start with '
- Invalid conflict URI '${raw}': wildcard 'conflict://*' does
- Invalid conflict URI '${raw}': must be 'conflict://<N>', 'co
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/d659ee8545fcc541.
Report an issue: GitHub.