can1357/oh-my-pi · error

message (pending separator error, justified-check failure)

Error message

message (pending separator error, justified-check failure)

What it means

When a pattern-only block was optimistically interpreted as a deletion (assumedDeletion, error 1220's sibling path), the parser records the would-be "needs separator" error as pending. After parsing, it keeps the deletion only if justified: some later operation's rewrite re-emits the deleted text verbatim (normalized) or references this operation via »N — i.e. it looks like the delete-half of a move. If not justified, the saved error message is thrown as-is.

Source

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

					throw new Error(
						`${REWRITE_HEADER}${reference[1]} must reference an earlier operation, not self/forward.`,
					);
				}
			}
		}
	}
	for (const [index, message] of pendingSeparatorErrors) {
		const patternNormalized = normalizeText(operations[index].patternText).text;
		const justified = operations.some((other, otherIndex) => {
			if (otherIndex === index) return false;
			const rewrites = other.rewrite.kind === "explicit" ? [other.rewrite.text] : other.rewrite.replacements;
			return rewrites.some(
				rewrite =>
					normalizeText(rewrite).text.includes(patternNormalized) ||
					rewrite.split("\n").some(line => line.trim() === `${REWRITE_HEADER}${index + 1}`),
			);
		});
		if (!justified) throw new Error(message);
	}
	return operations;
}

function normalizeText(source: string): NormalizedText {
	let text = "";
	const starts: number[] = [];
	const ends: number[] = [];
	for (let index = 0; index < source.length; ) {
		const codePoint = source.codePointAt(index);
		if (codePoint === undefined) break;
		if (codePoint <= 0x7f) {
			const next = index + 1;
			if (!((codePoint >= 0x09 && codePoint <= 0x0d) || codePoint === 0x20)) {
				text += source[index];
				starts.push(index);
				ends.push(next);
			}

View on GitHub (pinned to 9690622007)

Solutions

  1. Add an explicit «» separator: if it's a rewrite, put the new text after »; if it's a true deletion, leave the rewrite empty — explicit intent is always accepted.
  2. If this is part of a move, ensure the later operation's rewrite contains the deleted text (matching after normalization) or a »N reference to this operation.
  3. Paste the corrected payload from the thrown message, filling in <new text>.

Example fix

// before (ambiguous pattern-only block)
«
long block of text
«
new location text
»
long block of text

// after (explicit move)
«
long block of text
»
«
new location text
»
long block of text
Defensive patterns

Strategy: try-catch

Try / catch

try {
  const result = await computeSloppySectionDiff(section, cwd);
  if ("error" in result) {
    // this error surfaces with the full copy-ready corrected payload;
    // resubmit it with <new text> filled in (or keep the deletion explicit
    // via an empty rewrite after »)
  }
} catch (err) {
  if (err instanceof Error && err.message.includes("needs »")) { /* handle */ }
}

Prevention

When it happens

Trigger: A payload contains a multi-line pattern-only block (>= 24 normalized chars) with no «» separator, and no subsequent operation re-includes that text in its rewrite or issues »N pointing back at it — the parser cannot confirm the deletion was intentional.

Common situations: Models omitting «» for a true deletion of a long block (rejected as too risky to guess); intended moves where the re-emit didn't match exactly (whitespace/wording drift); deletions the author actually meant as rewrites.

Related errors


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