{"record":{"id":"0868237bbc6b3924","repo":"can1357/oh-my-pi","slug":"trimmed-is-valid-only-in-rewrite-never-match","errorCode":null,"errorMessage":"${trimmed} is valid only in REWRITE, never MATCH.","messagePattern":"(.+?) is valid only in REWRITE, never MATCH\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/edit/sloppy.ts","lineNumber":1358,"sourceCode":"\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.`);\n\t\t\t\t}\n\t\t\t\t// A lone »N after MATCH is always a mistyped » separator (observed\n\t\t\t\t// model idiolect); a genuine re-emit is authored as » then »N.\n\t\t\t\tstate = \"rewrite\";\n\t\t\t\treferenceSeparator = trimmed;\n\t\t\t} else if (parsedOpener !== false) {\n\t\t\t\t// A doubled opener produced an empty operation; drop it as noise.\n\t\t\t\tif (patternLines.some(entry => entry.trim() !== \"\")) finishPattern(index);\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} else {\n\t\t\t\tpatternLines.push(line);\n\t\t\t}\n\t\t\tcontinue;\n\t\t}\n","sourceCodeStart":1340,"sourceCodeEnd":1376,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/edit/sloppy.ts#L1340-L1376","documentation":"»N (a numbered reference to an earlier operation's rewrite, the \"register reference\") is only meaningful inside a REWRITE block where it pulls in a previous operation's replacement text. When the parser encounters a »N line while still in the MATCH state with an empty pattern — i.e. before any MATCH content exists — it throws this error. A »N after non-empty MATCH content is tolerated as a mistyped » separator (a known model idiolect) and treated as the rewrite boundary instead.","triggerScenarios":"A payload containing »1/»2/... as the first line of an operation — directly after the « opener with no pattern lines — e.g. a model trying to reference a prior rewrite without restating the match.","commonSituations":"LLMs chaining operations via register references but forgetting the MATCH block; confusion between the »N reference syntax and the «» separator; compressed multi-edit payloads that skip restating context.","solutions":["Add the MATCH pattern under « before the »N line — every operation needs its own match text.","If you meant to separate MATCH from REWRITE, use a bare » line instead of »N.","If the intent was to reuse an earlier replacement, write the pattern, then », then »N inside the rewrite text."],"exampleFix":"// before\n«\n»1\n\n// after\n«\npattern text\n»\n»1","handlingStrategy":"validation","validationCode":"// »N must appear only after non-empty pattern content\nlet state = \"outside\", hasPattern = false;\nfor (const l of body.split(\"\\n\")) {\n  const t = l.trim();\n  if (/^»[1-9]\\d*$/.test(t) && state === \"pattern\" && !hasPattern) throw new Error(\"»N needs a MATCH pattern first\");\n  if (t === \"«\" || t === \"«*\") { state = \"pattern\"; hasPattern = false; }\n  else if (state === \"pattern\" && t !== \"\") hasPattern = true;\n}","typeGuard":"const isRegisterReference = (line: string): boolean => /^»[1-9]\\d*$/.test(line.trim());","tryCatchPattern":"catch (err) {\n  if (err instanceof Error && err.message.includes(\"valid only in REWRITE, never MATCH\")) {\n    // inject the missing pattern lines before the »N line and retry\n  }\n}","preventionTips":["Every operation must restate its MATCH text even when reusing a prior rewrite via »N.","Emit register references inside the rewrite section only: pattern, », then »N.","When templating chained edits, always include the pattern block."],"tags":["parser","edit-format","register-reference"],"backgroundTag":"invalid-opener-syntax","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}