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
- Re-read without offset (or offset 1) to see the file's current contents and real length
- Derive the next offset from the tool's own truncation hint in the latest read, never from stale history
- Clamp the requested offset to the reported total before retrying
- 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
- Always derive continuation offsets from the read tool's own 'Use offset=N' hint in the same response
- Re-read from offset 1 whenever a file may have changed between reads
- Treat an unexpected line-count shrink as a signal that something rewrote the file
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.