{"record":{"id":"44bd672d75622e52","repo":"can1357/oh-my-pi","slug":"operation-operationnumber-has-an-unmatched-clos","errorCode":null,"errorMessage":"Operation ${operationNumber} has an unmatched closing selection marker ⟫; add opening ⟪.","messagePattern":"Operation (.+?) has an unmatched closing selection marker ⟫; add opening ⟪\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/edit/sloppy.ts","lineNumber":648,"sourceCode":"}\n\nfunction parseInlinePattern(\n\tpattern: string,\n\toperationNumber: number,\n): { patternText: string; replacements: string[]; notes: string[] } {\n\tvalidateSelectionMarkers(pattern, operationNumber);\n\tlet patternText = \"\";\n\tconst replacements: string[] = [];\n\tconst notes: string[] = [];\n\tlet sawBare = false;\n\tlet sawInline = false;\n\n\tfor (let index = 0; index < pattern.length; ) {\n\t\tconst codePoint = pattern.codePointAt(index);\n\t\tif (codePoint === undefined) break;\n\t\tconst character = String.fromCodePoint(codePoint);\n\t\tif (character === SELECT_CLOSE) {\n\t\t\tthrow new Error(`Operation ${operationNumber} has an unmatched closing selection marker ⟫; add opening ⟪.`);\n\t\t}\n\t\tif (character !== SELECT_OPEN) {\n\t\t\tpatternText += character;\n\t\t\tindex += character.length;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst close = pattern.indexOf(SELECT_CLOSE, index + character.length);\n\t\tconst selected = pattern.slice(index + character.length, close);\n\t\tif (selected.includes(SELECT_OPEN)) {\n\t\t\tthrow new Error(\n\t\t\t\t`Operation ${operationNumber} has nested selection markers; use one selection per replacement.`,\n\t\t\t);\n\t\t}\n\t\tconst divider = selected.indexOf(SELECT_DIVIDER);\n\t\tif (divider === -1) {\n\t\t\tsawBare = true;\n\t\t\tpatternText += pattern.slice(index, close + SELECT_CLOSE.length);","sourceCodeStart":630,"sourceCodeEnd":666,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/edit/sloppy.ts#L630-L666","documentation":"While expanding selections, the scanner walks the pattern character by character; if it encounters a closing ⟫ before any opening ⟪, the closing marker has no counterpart. This is the character-level equivalent of the count-based check and catches closings that appear before their opener in stream order.","triggerScenarios":"applySloppy operation pattern where an ⟫ occurs at a position with no preceding active ⟪ (e.g. pattern starts with ⟫, or a previous selection already consumed its opener).","commonSituations":"Reordered or duplicated marker characters when an LLM edits a payload; quoting code that itself contains ⟫; manual payload hand-editing that moved the close before the open.","solutions":["Move the ⟫ to after the text it should close (after the matching ⟪… span)","Add the missing ⟪ before the text you intend to select","Remove the stray ⟫ if no selection was intended"],"exampleFix":"// before\n«old ⟫text⟪»new\n// after\n«old ⟪text⟫»new","handlingStrategy":"validation","validationCode":"let depth = 0;\nfor (const ch of pattern) {\n  if (ch === '⟪') depth++;\n  if (ch === '⟫') { if (depth === 0) throw new Error('⟫ before ⟪'); depth--; }\n}\nif (depth !== 0) throw new Error('unclosed ⟪');","typeGuard":null,"tryCatchPattern":"try {\n  applySloppy(content, input, ctx);\n} catch (err) {\n  if (err instanceof Error && err.message.includes('unmatched closing selection marker')) {\n    // reorder/remove the stray ⟫ and re-send\n  } else throw err;\n}","preventionTips":["Ensure each ⟫ appears strictly after its ⟪ in stream order","Escape or avoid quoting code that contains ⟫ literally inside selections","Validate marker ordering with a depth counter before submitting"],"tags":["sloppy","selection-markers","payload-validation"],"backgroundTag":"unbalanced-selection-markers","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}