can1357/oh-my-pi · error · Error

Line ${anchor.line} does not exist (file has ${fileLines.len

Error message

Line ${anchor.line} does not exist (file has ${fileLines.length} lines)

What it means

validateLineBounds checks every edit anchor's line number against the actual file length after splitting the file into lines. If any anchor references a line below 1 or beyond EOF, applyEdits throws rather than silently skipping or corrupting the patch. Header-hash checks happen earlier, but line numbers can still drift when the on-disk file changed since the model last read it.

Source

Thrown at packages/hashline/src/apply.ts:85

	// thing and delete through the last concrete line.
	return fileLines.length > 1 && fileLines[fileLines.length - 1] === "" ? fileLines.length : 0;
}

function dropTrailingPhantomDeletes(edits: AppliedEdit[], fileLines: readonly string[]): AppliedEdit[] {
	const phantomLine = trailingPhantomLine(fileLines);
	if (phantomLine === 0) return edits;
	return edits.filter(edit => edit.kind !== "delete" || edit.anchor.line !== phantomLine);
}

/**
 * Verify every anchored edit points at an existing line. File-version binding is
 * checked once per section via the header hash before this function runs.
 */
function validateLineBounds(edits: readonly AppliedEdit[], fileLines: readonly string[]): void {
	for (const edit of edits) {
		for (const anchor of getEditAnchors(edit)) {
			if (anchor.line < 1 || anchor.line > fileLines.length) {
				throw new Error(`Line ${anchor.line} does not exist (file has ${fileLines.length} lines)`);
			}
		}
	}
}

function cloneAppliedEdit(edit: AppliedEdit, index: number): AppliedEdit {
	if (edit.kind === "delete") return { ...edit, anchor: { ...edit.anchor }, index };
	return { ...edit, cursor: cloneCursor(edit.cursor), index };
}

function insertAtStart(fileLines: string[], lineOrigins: LineOrigin[], lines: string[]): void {
	if (lines.length === 0) return;
	const origins = lines.map((): LineOrigin => "insert");
	if (fileLines.length === 1 && fileLines[0] === "") {
		fileLines.splice(0, 1, ...lines);
		lineOrigins.splice(0, 1, ...origins);
		return;
	}

View on GitHub (pinned to 9690622007)

Solutions

  1. Re-read the file to get fresh line numbers, then regenerate the hashline edit with valid anchors
  2. Clamp/verify anchor.line against the file length before calling applyEdits
  3. Re-run the edit workflow so the header hash and line range match the current file
  4. If the file shrank intentionally, rewrite it wholesale with the write tool instead of a line patch

Example fix

// before
await applyEdits(path, edits); // throws if line 500 gone
// after
const lines = (await Bun.file(path).text()).split("\n");
const safe = edits.filter(e => getEditAnchors(e).every(a => a.line >= 1 && a.line <= lines.length));
await applyEdits(path, safe);
Defensive patterns

Strategy: validation

Validate before calling

const lines = (await Bun.file(path).text()).split("\n");
for (const e of edits) {
  for (const a of getEditAnchors(e)) {
    if (a.line < 1 || a.line > lines.length) {
      throw new Error(`stale anchor: line ${a.line} not in 1..${lines.length}`);
    }
  }
}

Try / catch

try {
  await applyEdits(path, edits);
} catch (e) {
  if (e.message.includes("does not exist")) {
    // re-read file and regenerate the patch with fresh line numbers
  }
}

Prevention

When it happens

Trigger: Calling applyEdits with a section whose anchor line exceeds the file's line count — the file was shortened since it was read, a hashline was hand-written, or an off-by-one in generating the line number.

Common situations: Another tool/agent truncated or rewrote the file between read and edit, applying edits captured against an older file revision, or the model hallucinating a line number in a patch.

Related errors


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