{"record":{"id":"4e5a065cc7be27a9","repo":"can1357/oh-my-pi","slug":"expected-opener-on-input-line-index-1","errorCode":null,"errorMessage":"Expected ${OPENER} on input line ${index + 1}.","messagePattern":"Expected (.+?) on input line (.+?)\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/edit/sloppy.ts","lineNumber":1340,"sourceCode":"\t\t}\n\t\tif (\n\t\t\tparsedOpener === false &&\n\t\t\t(trimmed.startsWith(OPENER) ||\n\t\t\t\t(trimmed.startsWith(REWRITE_HEADER) && trimmed !== REWRITE_HEADER && !registerReference))\n\t\t) {\n\t\t\tthrow new Error(\n\t\t\t\t`Invalid control line ${JSON.stringify(trimmed)}; use only ${OPENER}, ${OPENER}*, ${REWRITE_HEADER}, or ${REWRITE_HEADER}N in REWRITE.`,\n\t\t\t);\n\t\t}\n\t\tif (state === \"outside\") {\n\t\t\tif (parsedOpener !== false) {\n\t\t\t\tallMatches = parsedOpener === 0;\n\t\t\t\tpatternLines = [];\n\t\t\t\trewriteLines = [];\n\t\t\t\treferenceSeparator = undefined;\n\t\t\t\tstate = \"pattern\";\n\t\t\t} else if (trimmed !== \"\") {\n\t\t\t\tthrow new Error(`Expected ${OPENER} on input line ${index + 1}.`);\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n\n\t\tif (state === \"pattern\") {\n\t\t\tconst accumulated = patternLines.join(\"\\n\");\n\t\t\tconst markersBalanced = (accumulated.match(/⟪/gu) || []).length === (accumulated.match(/⟫/gu) || []).length;\n\t\t\tif (trimmed === REWRITE_HEADER) {\n\t\t\t\tstate = \"rewrite\";\n\t\t\t} else if (trimmed === \"***\" && markersBalanced && patternLines.some(entry => entry.trim() !== \"\")) {\n\t\t\t\t// A bare *** after MATCH content is an apply-patch-habit separator.\n\t\t\t\tstate = \"rewrite\";\n\t\t\t} else if (trimmed === SELECT_CLOSE && markersBalanced && patternLines.some(entry => entry.trim() !== \"\")) {\n\t\t\t\t// A lone ⟫ after balanced selections is a mistyped » separator.\n\t\t\t\tstate = \"rewrite\";\n\t\t\t} else if (registerReference && markersBalanced) {\n\t\t\t\tif (!patternLines.some(entry => entry.trim() !== \"\")) {\n\t\t\t\t\tthrow new Error(`${trimmed} is valid only in REWRITE, never MATCH.`);","sourceCodeStart":1322,"sourceCodeEnd":1358,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/edit/sloppy.ts#L1322-L1358","documentation":"The parser starts in the \"outside\" state: before the first « opener, the only legal non-empty input is the opener itself. Any other non-blank line before the first operation is rejected with this error, stating the 1-based input line number where the unexpected content was found. Blank lines are tolerated so leading whitespace does not break payloads.","triggerScenarios":"Passing a section body that starts with prose, a file path line, a diff header (--- a/...), or any other text before the first « opener line to the sloppy apply routine.","commonSituations":"Models prefixing payloads with explanations or «file» headers; tooling stripping the opener when slicing the body; pasted payloads that lost the first « line; bodies that include diff-style headers.","solutions":["Start the payload with a « (or §path + «) opener line before any content.","Delete any preamble text/prose before the first opener.","If the content before the first opener is meaningful, move it inside the first operation's MATCH block."],"exampleFix":"// before\nHere are the changes:\n«\npattern\n»\nreplacement\n\n// after\n«\npattern\n»\nreplacement","handlingStrategy":"validation","validationCode":"const firstContent = body.split(\"\\n\").find(l => l.trim() !== \"\");\nif (firstContent !== undefined && !(firstContent.trim() === \"«\" || firstContent.trim() === \"«*\" || /^§/.test(firstContent.trim()))) {\n  throw new Error(\"payload must start with a « opener\");\n}","typeGuard":"const startsWithOpener = (body: string): boolean => {\n  const l = body.split(\"\\n\").find(x => x.trim() !== \"\");\n  return l !== undefined && (l.trim() === \"«\" || l.trim() === \"«*\");\n};","tryCatchPattern":"catch (err) {\n  if (err instanceof Error && err.message.includes(\"Expected « on input line\")) {\n    // drop leading prose/headers before the first « and retry\n  }\n}","preventionTips":["Begin every payload directly with « — no explanations, headers, or diff markers before it.","Trim prose from LLM output before passing the body to the applier.","Use §path headers (which the section splitter consumes) for file targeting."],"tags":["parser","edit-format","payload-structure"],"backgroundTag":"invalid-opener-syntax","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}