{"record":{"id":"90b594a5f21b4e92","repo":"can1357/oh-my-pi","slug":"diff-contains-multifilecount-file-markers-sing","errorCode":null,"errorMessage":"Diff contains ${multiFileCount} file markers. Single-file patches cannot contain multi-file markers.","messagePattern":"Diff contains (.+?) file markers\\. Single-file patches cannot contain multi-file markers\\.","errorType":"validation","errorClass":"ApplyPatchError","httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/edit/diff.ts","lineNumber":809,"sourceCode":"\t\tif (!candidate) return undefined;\n\t\treturn candidate.replace(/^(a|b)\\//, \"\");\n\t}\n\tif (line.startsWith(\"*** Update File:\")) {\n\t\treturn line.slice(\"*** Update File:\".length).trim();\n\t}\n\tif (line.startsWith(\"*** Add File:\")) {\n\t\treturn line.slice(\"*** Add File:\".length).trim();\n\t}\n\tif (line.startsWith(\"*** Delete File:\")) {\n\t\treturn line.slice(\"*** Delete File:\".length).trim();\n\t}\n\treturn undefined;\n}\n\nexport function parseDiffHunks(diff: string): DiffHunk[] {\n\tconst multiFileCount = countMultiFileMarkers(diff);\n\tif (multiFileCount > 1) {\n\t\tthrow new ApplyPatchError(\n\t\t\t`Diff contains ${multiFileCount} file markers. Single-file patches cannot contain multi-file markers.`,\n\t\t);\n\t}\n\n\tconst normalizedDiff = normalizeDiff(diff);\n\tconst lines = normalizedDiff.split(\"\\n\");\n\tconst hunks: DiffHunk[] = [];\n\tlet i = 0;\n\n\twhile (i < lines.length) {\n\t\tconst line = lines[i];\n\t\tconst trimmed = line.trim();\n\n\t\tif (trimmed === \"\") {\n\t\t\ti++;\n\t\t\tcontinue;\n\t\t}\n","sourceCodeStart":791,"sourceCodeEnd":827,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/edit/diff.ts#L791-L827","documentation":"parseDiffHunks is the single-file patch parser: it counts multi-file markers (e.g. '*** Update File:' / git 'diff --git' style headers) in the input and throws ApplyPatchError if more than one is present. Multi-file patches must go through the multi-file parser; feeding them here would silently merge changes intended for different files into one hunk list.","triggerScenarios":"Calling parseDiffHunks with a patch containing two or more file markers — e.g. a patch updating two files ('*** Update File: a.ts' ... '*** Update File: b.ts') or concatenated git diffs for multiple files.","commonSituations":"LLM agents emitting a multi-file patch where a single-file one was expected; concatenating several per-file diffs before parsing; choosing the single-file API by mistake when the patch legitimately touches multiple files.","solutions":["Route the patch to the multi-file parser instead of parseDiffHunks.","Split the patch on its file markers and call parseDiffHunks once per single-file section.","If only one file should change, delete the extra file sections from the patch.","Fix the prompt/tool contract so the model emits one file per patch invocation."],"exampleFix":"// before\nparseDiffHunks(patchWithTwoFileMarkers)\n// after\nfor (const section of splitByFileMarkers(patch)) parseDiffHunks(section)","handlingStrategy":"validation","validationCode":"const markers = diff.match(/^\\*{3} (?:Update|Add|Delete) File:|^diff --git /gm) ?? [];\nif (markers.length > 1) {\n  throw new Error(`Patch contains ${markers.length} files; split it before parseDiffHunks`);\n}","typeGuard":null,"tryCatchPattern":"try {\n  hunks = parseDiffHunks(diff);\n} catch (err) {\n  if (err instanceof ApplyPatchError && err.message.includes(\"file markers\")) {\n    // switch to the multi-file parser or split on the markers and parse per file\n  } else throw err;\n}","preventionTips":["Match the parser to the patch shape: multi-file patches go to the multi-file parser.","Constrain LLM agents to one file per patch call when using the single-file API.","Split concatenated diffs on their file markers before parsing each section."],"tags":["diff","parsing","api-misuse"],"backgroundTag":"multi-file-patch-in-single-file-parser","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}