can1357/oh-my-pi · error

${trimmed} is valid only in REWRITE, never MATCH.

Error message

${trimmed} is valid only in REWRITE, never MATCH.

What it means

»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.

Source

Thrown at packages/coding-agent/src/edit/sloppy.ts:1358

				throw new Error(`Expected ${OPENER} on input line ${index + 1}.`);
			}
			continue;
		}

		if (state === "pattern") {
			const accumulated = patternLines.join("\n");
			const markersBalanced = (accumulated.match(/⟪/gu) || []).length === (accumulated.match(/⟫/gu) || []).length;
			if (trimmed === REWRITE_HEADER) {
				state = "rewrite";
			} else if (trimmed === "***" && markersBalanced && patternLines.some(entry => entry.trim() !== "")) {
				// A bare *** after MATCH content is an apply-patch-habit separator.
				state = "rewrite";
			} else if (trimmed === SELECT_CLOSE && markersBalanced && patternLines.some(entry => entry.trim() !== "")) {
				// A lone ⟫ after balanced selections is a mistyped » separator.
				state = "rewrite";
			} else if (registerReference && markersBalanced) {
				if (!patternLines.some(entry => entry.trim() !== "")) {
					throw new Error(`${trimmed} is valid only in REWRITE, never MATCH.`);
				}
				// A lone »N after MATCH is always a mistyped » separator (observed
				// model idiolect); a genuine re-emit is authored as » then »N.
				state = "rewrite";
				referenceSeparator = trimmed;
			} else if (parsedOpener !== false) {
				// A doubled opener produced an empty operation; drop it as noise.
				if (patternLines.some(entry => entry.trim() !== "")) finishPattern(index);
				allMatches = parsedOpener === 0;
				patternLines = [];
				rewriteLines = [];
				referenceSeparator = undefined;
			} else {
				patternLines.push(line);
			}
			continue;
		}

View on GitHub (pinned to 9690622007)

Solutions

  1. Add the MATCH pattern under « before the »N line — every operation needs its own match text.
  2. If you meant to separate MATCH from REWRITE, use a bare » line instead of »N.
  3. If the intent was to reuse an earlier replacement, write the pattern, then », then »N inside the rewrite text.

Example fix

// before
«
»1

// after
«
pattern text
»
»1
Defensive patterns

Strategy: validation

Validate before calling

// »N must appear only after non-empty pattern content
let state = "outside", hasPattern = false;
for (const l of body.split("\n")) {
  const t = l.trim();
  if (/^»[1-9]\d*$/.test(t) && state === "pattern" && !hasPattern) throw new Error("»N needs a MATCH pattern first");
  if (t === "«" || t === "«*") { state = "pattern"; hasPattern = false; }
  else if (state === "pattern" && t !== "") hasPattern = true;
}

Type guard

const isRegisterReference = (line: string): boolean => /^»[1-9]\d*$/.test(line.trim());

Try / catch

catch (err) {
  if (err instanceof Error && err.message.includes("valid only in REWRITE, never MATCH")) {
    // inject the missing pattern lines before the »N line and retry
  }
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/0868237bbc6b3924. Report an issue: GitHub.