can1357/oh-my-pi · error

${REWRITE_HEADER}${reference[1]} must reference an earlier o

Error message

${REWRITE_HEADER}${reference[1]} must reference an earlier operation, not self/forward.

What it means

Inside a rewrite, a »N line reuses the replacement text of operation N — but only backwards: N must be strictly less than the current operation number (operations are 1-based, so line »2 inside operation 2 is self-reference and rejected). Self- and forward-references would be circular or undefined at apply time, so the parser rejects them up front during post-parse validation.

Source

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

		) {
			// 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++) {
		const operationRewrite = operations[index].rewrite;
		const rewrites = operationRewrite.kind === "explicit" ? [operationRewrite.text] : operationRewrite.replacements;
		for (const rewrite of rewrites) {
			for (const line of rewrite.split("\n")) {
				const reference = line.trim().match(/^»([1-9]\d*)$/u);
				if (reference && Number(reference[1]) >= index + 1) {
					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);

View on GitHub (pinned to 9690622007)

Solutions

  1. Change the reference to point at a strictly earlier operation (renumber if you reordered operations).
  2. Inline the desired text directly instead of referencing if no earlier operation holds it.
  3. Recount operations from 1 in payload order and fix all »N lines to satisfy N < current.

Example fix

// before (operation 3 referencing itself)
«
pattern
»
»3

// after
«
pattern
»
»1
Defensive patterns

Strategy: validation

Validate before calling

let op = 0;
for (const line of body.split("\n")) {
  const t = line.trim();
  if (t === "«" || t === "«*") op++;
  const ref = t.match(/^»([1-9]\d*)$/);
  if (ref && Number(ref[1]) >= op && op > 0) throw new Error(`»${ref[1]} is not an earlier operation (current: ${op})`);
}

Type guard

const isBackwardReference = (n: number, currentOp: number): boolean => n > 0 && n < currentOp;

Try / catch

catch (err) {
  if (err instanceof Error && err.message.includes("must reference an earlier operation")) {
    // renumber »N references after any operation reordering
  }
}

Prevention

When it happens

Trigger: A rewrite block (explicit text or a »-reference chain) containing a »N line where N >= the current operation's index+1 — e.g. operation 1 containing »1, or operation 3 containing »4.

Common situations: Models numbering references from 0 or miscounting operation order; copying a register line into the operation it defines; reordering operations after writing references without renumbering.

Related errors


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