earendil-works/pi · error

Offset ${offset} is beyond end of file (${allLines.length} l

Error message

Offset ${offset} is beyond end of file (${allLines.length} lines total)

What it means

The read tool validates its 1-indexed offset against the line count from splitting the decoded file on \n (which includes a trailing empty element when the file ends with a newline, and yields length 1 for an empty file). If offset-1 >= that count, the read cannot start and the tool throws with the real total. It is a pagination mistake by the caller or model, not an environment fault; note the trailing element makes the check slightly lenient, so an off-by-one often returns an empty line instead of throwing, and one more triggers this error.

Source

Thrown at packages/agent/src/harness/tools/read.ts:103

						details: undefined,
					};
				}
				return {
					content: [
						{ type: "text", text: `Read image file [${mimeType}]` },
						{ type: "image", data: encodeBase64(bytes), mimeType },
					] satisfies Array<TextContent | ImageContent>,
					details: undefined,
				};
			}

			const textContent = new TextDecoder().decode(bytes);
			const allLines = textContent.split("\n");
			const totalFileLines = allLines.length;
			const startLine = offset ? Math.max(0, offset - 1) : 0;
			const startLineDisplay = startLine + 1;
			if (startLine >= allLines.length) {
				throw new Error(`Offset ${offset} is beyond end of file (${allLines.length} lines total)`);
			}

			let selectedContent: string;
			let userLimitedLines: number | undefined;
			if (limit !== undefined) {
				const endLine = Math.min(startLine + limit, allLines.length);
				selectedContent = allLines.slice(startLine, endLine).join("\n");
				userLimitedLines = endLine - startLine;
			} else {
				selectedContent = allLines.slice(startLine).join("\n");
			}

			const truncation = truncateHead(selectedContent);
			let outputText: string;
			let details: ReadToolDetails | undefined;
			if (truncation.firstLineExceedsLimit) {
				const firstLineSize = formatSize(new TextEncoder().encode(allLines[startLine]).byteLength);
				outputText = `[Line ${startLineDisplay} is ${firstLineSize}, exceeds ${formatSize(DEFAULT_MAX_BYTES)} limit. Use bash: sed -n '${startLineDisplay}p' ${path} | head -c ${DEFAULT_MAX_BYTES}]`;

View on GitHub (pinned to 4af9d21d3b)

Solutions

  1. Re-read without offset (or offset 1) to see the file's current contents and real length
  2. Derive the next offset from the tool's own truncation hint in the latest read, never from stale history
  3. Clamp the requested offset to the reported total before retrying
  4. If the file should have been longer, check whether a concurrent write or truncate changed it under you

Example fix

// before
const res = await readTool.execute(id, { path, offset: nextOffset }, signal, undefined, ctx);

// after - clamp offset against the file's current line count
const r = await env.readTextFile(absPath, signal);
const total = r.ok ? r.value.split("\n").length : 0;
const offset = nextOffset && nextOffset > total ? undefined : nextOffset;
const res = await readTool.execute(id, { path, offset }, signal, undefined, ctx);
Defensive patterns

Strategy: validation

Validate before calling

const r = await env.readTextFile(absolutePath, signal);
if (r.ok) {
  const totalLines = r.value.split("\n").length;
  if (offset !== undefined && offset - 1 >= totalLines) {
    offset = undefined; // or Math.max(1, Math.min(offset, totalLines))
  }
}
await readTool.execute(id, { path, offset, limit }, signal, undefined, ctx);

Type guard

const isValidReadOffset = (offset: number | undefined, totalLines: number): boolean =>
  offset === undefined || (Number.isInteger(offset) && offset >= 1 && offset - 1 < totalLines);

Try / catch

try {
  return await readTool.execute(id, input, signal, undefined, ctx);
} catch (e) {
  if (e instanceof Error && /Offset \d+ is beyond end of file/.test(e.message)) {
    return readTool.execute(id, { ...input, offset: undefined }, signal, undefined, ctx);
  }
  throw e;
}

Prevention

When it happens

Trigger: Passing offset larger than the file's line count (offset 50 on a 20-line file); continuing a truncated read with a next-offset computed from an older, longer version of the file; offset >= 2 on an empty file (split of empty string has length 1).

Common situations: Agent loops that resume reading with a stale 'Use offset=N to continue' hint after the file was rewritten or truncated; hardcoded offsets in scripts; files that shrink between reads because another process or tool rewrote them.

Related errors


AI-assisted analysis of earendil-works/pi@4af9d21d3b (2026-08-24). Data as JSON: /api/errors/7ed3563221e1503f. Report an issue: GitHub.