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
- Remove the duplicate » line; keep exactly one per operation.
- Merge both replacement chunks into a single block after one ».
- 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
- One » per operation — never split a rewrite across multiple » blocks.
- Don't wrap MATCH and REWRITE in « ... »; the format is « ... » ... not bracketed.
- Lint generated payloads: count » lines and compare against « count.
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
- needsSeparator (Operation ${operations.length + 1} needs ${R
- ${trimmed} is not a valid opener. Use ${OPENER} with a patte
- Invalid control line ${JSON.stringify(trimmed)}; use only ${
- Expected ${OPENER} on input line ${index + 1}.
- ${trimmed} is valid only in REWRITE, never MATCH.
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/a9c063c557af03fe.
Report an issue: GitHub.