{"record":{"id":"d659ee8545fcc541","repo":"can1357/oh-my-pi","slug":"malformed-conflict-per-id-block-stray-len","errorCode":null,"errorMessage":"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).","messagePattern":"Malformed `conflict://\\*` per-id block: (.+?) line\\(s\\) are not `<id>: @side` directives \\(first: `(.+?)`\\)\\. (.+?)Literal or multi-line replacement content isn't supported in a per-id block — resolve those blocks with individual `write\\((.+?)\\)` 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\\)\\.","errorType":"validation","errorClass":"ToolError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/tools/write.ts","lineNumber":265,"sourceCode":"\t\tif (!match) {\n\t\t\tstray.push(line);\n\t\t\tcontinue;\n\t\t}\n\t\tsawDirective = true;\n\t\tconst id = Number.parseInt(match[1], 10);\n\t\tif (map.has(id)) {\n\t\t\tthrow new ToolError(`Bulk directive lists conflict #${id} twice — each id may appear once.`);\n\t\t}\n\t\tmap.set(id, match[2]);\n\t}\n\t// No directive lines at all → not a per-id block; caller uses uniform mode.\n\tif (!sawDirective) return null;\n\tif (stray.length > 0) {\n\t\tconst sample = stray[0]!;\n\t\tconst tokenHint = BULK_DIRECTIVE_HEAD_RE.test(sample)\n\t\t\t? `Per-id bulk only accepts the tokens @ours/@theirs/@base/@both — one side per id, single line. `\n\t\t\t: \"\";\n\t\tthrow new ToolError(\n\t\t\t`Malformed \\`conflict://*\\` per-id block: ${stray.length} line(s) are not \\`<id>: @side\\` directives (first: \\`${truncateDirectiveLine(sample)}\\`). ` +\n\t\t\t\ttokenHint +\n\t\t\t\t`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). ` +\n\t\t\t\t`For a pure pick-a-side pass, make every non-empty line \\`<id>: @ours\\` (or @theirs/@base/@both).`,\n\t\t);\n\t}\n\treturn map;\n}\n\n/**\n * Resolve per-id directives, preferring the pre-strip `raw` content and falling\n * back to the hashline-stripped `stripped` content.\n *\n * Raw is preferred because the `<id>:` directive heads look exactly like\n * hashline `LINE:` prefixes and would be eaten by stripping. When the two\n * contents are identical (hashline mode off) a single parse decides everything,\n * so a malformed-block error propagates straight through — the previous\n * `?? parseBulkDirectives(...)` chain would have swallowed it and silently","sourceCodeStart":247,"sourceCodeEnd":283,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/tools/write.ts#L247-L283","documentation":"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.","triggerScenarios":"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.","commonSituations":"Models trying to supply replacement file content inline in the bulk block, or pasting conflict hunks into the per-id list.","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"],"exampleFix":"// before\n\"1: @ours\\n2: replace whole file with ...\"\n// after\nwrite({ path: \"conflict://*\", content: \"1: @ours\" })\nwrite({ path: \"conflict://2\", content: \"replacement content\" })","handlingStrategy":"validation","validationCode":"const DIRECTIVE_RE = /^\\s*\\d+:\\s*@(ours|theirs|base|both)\\s*$/;\nfunction isPurePerIdBlock(block: string): boolean {\n  return block.split(\"\\n\").filter(l => l.trim().length > 0).every(l => DIRECTIVE_RE.test(l));\n}\n// if false, split out literal content into individual conflict://<N> writes","typeGuard":null,"tryCatchPattern":"try {\n  await write({ path: \"conflict://*\", content: block });\n} catch (e) {\n  if (e instanceof ToolError && e.message.includes(\"per-id block\")) {\n    const { directives, literals } = splitBlock(block);\n    await write({ path: \"conflict://*\", content: directives });\n    for (const [id, text] of literals) await write({ path: `conflict://${id}`, content: text });\n    return;\n  }\n  throw e;\n}","preventionTips":["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"],"tags":["conflict-resolution","bulk-directives","malformed-input"],"backgroundTag":"malformed-bulk-directive","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}