can1357/oh-my-pi · error

Operation ${operations.length + 1} has a second ${REWRITE_HE

Error message

Operation ${operations.length + 1} has a second ${REWRITE_HEADER} line.

What it means

Each operation may contain exactly one » (REWRITE) header. When the parser sees a second bare » inside the rewrite state AND the next non-blank line is not a new « opener, it concludes a second rewrite block was attempted for the same operation and throws. If the next content line is a valid opener, the stray » is instead forgiven as a close-bracket artifact (models sometimes wrap MATCH and REWRITE in «...»).

Source

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

			} else {
				patternLines.push(line);
			}
			continue;
		}

		if (parsedOpener !== false) {
			finish(index);
			allMatches = parsedOpener === 0;
			patternLines = [];
			rewriteLines = [];
			referenceSeparator = undefined;
			state = "pattern";
		} else if (trimmed === "***") {
			// Stray apply-patch structure inside REWRITE; never final text.
		} else if (trimmed === REWRITE_HEADER) {
			const nextContent = lines.slice(index + 1).find(entry => entry.trim() !== "");
			if (nextContent !== undefined && parseOpener(nextContent) === false) {
				throw new Error(`Operation ${operations.length + 1} has a second ${REWRITE_HEADER} line.`);
			}
			// A bare » before the next operation or at payload end is a stray
			// close-bracket: models sometimes wrap both MATCH and REWRITE in «…».
		} else if (
			trimmed === SELECT_CLOSE &&
			(rewriteLines.join("\n").match(/⟪/gu) || []).length === (rewriteLines.join("\n").match(/⟫/gu) || []).length
		) {
			// A lone ⟫ with no open selection is a stray block terminator; REWRITE
			// is final text and never carries selection markers.
		} else {
			rewriteLines.push(line);
		}
	}

	if (state === "rewrite") finish(lines.length);
	else if (state === "pattern") finishPattern(lines.length);
	if (operations.length === 0) throw new Error(`Empty patch. Start with ${OPENER}.`);
	for (let index = 0; index < operations.length; index++) {

View on GitHub (pinned to 9690622007)

Solutions

  1. Remove the duplicate » line; keep exactly one per operation.
  2. Merge both replacement chunks into a single block after one ».
  3. If a second change is intended, start a new operation with its own « opener.

Example fix

// before
«
pattern
»
first part
»
second part

// after
«
pattern
»
first part
second part
Defensive patterns

Strategy: validation

Validate before calling

const sepCount = (body.match(/^»$/gm) || []).length;
const opCount = (body.match(/^«\*?$/gm) || []).length;
if (sepCount > opCount + /* tolerated artifacts */ 0) console.warn("possible duplicate » line");

Type guard

const exactlyOneSeparator = (op: string[]): boolean => op.filter(l => l.trim() === "»").length === 1;

Try / catch

catch (err) {
  if (err instanceof Error && err.message.includes("has a second » line")) {
    // merge the split rewrite blocks and resubmit
  }
}

Prevention

When it happens

Trigger: An operation whose rewrite text is followed by another bare » line with more non-opener content after it — e.g. the author splitting the replacement into two »-delimited chunks, or double-closing with » ... » followed by more text.

Common situations: Models wrapping both halves in « ... » and emitting two closing markers; splitting a large rewrite into multiple » sections; copy-paste duplicating the separator line.

Related errors


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