Skip to content

plan: read_file continuation hint when line-window clips the file (#725) #731

Description

@btipling

Status: IMPLEMENTED

Target

lib/agent/tools.tsread_file tool result rendering (lines 480–505).

Current baseline (verified against live code at 9d133fb)

Claim Location Verdict
formatLineWindow returns { body, returned, totalLines } — no metadata tools.ts:114-128
read_file execute already has result.truncated (byte-level) tools.ts:482
read_file execute has offset + limit + window.returned + window.totalLines available tools.ts:478-487
read_file output builds header + window.body at the end tools.ts:502-504
isFullFileReadGrant still has returned >= totalLines on main tools.ts:138 ⚠️ #730 PR open, not yet merged

Root cause

When read_file clips at the default 1000-line limit, the agent sees only the first 1000 lines with no indication the file continues. The agent must infer truncation from the missing grant — and once #730 lands the grant succeeds anyway (offset=1, no byte truncation), so the agent never knows it missed lines.

Fix: one-line metadata footer

Append a continuation hint to the read_file output body (in the read_file execute body, not inside formatLineWindow) when the line window clips the file AND the read was not byte-truncated:

[File continues beyond line 1000 — 1001..1121: use offset=1001 to read the remaining 121 lines]

Logic (lives in read_file execute body after formatLineWindow, lines ~502–504)

// After: const flag = fullGrant ? '' : ' (truncated)';
// After: const ann = formatCwdAnnotation(cwdSnap);
let body = window.body;
if (!byteTruncated && window.returned < window.totalLines) {
  const nextLine = offset + window.returned; // 1-based
  const remaining = window.totalLines - nextLine + 1;
  body += `\n[File continues beyond line ${offset + window.returned - 1}${nextLine}..${window.totalLines}: use offset=${nextLine} to read the remaining ${remaining} line${remaining === 1 ? '' : 's'}]`;
}

Why in the execute body, not formatLineWindow: formatLineWindow(content, offset, limit) doesn't receive byteTruncated. The byte-truncation gate must be checked before appending the hint. Putting the logic in the execute body (where result.truncated is already available) keeps formatLineWindow's signature unchanged and the gate correct.

Edge cases

Scenario Hint shown?
offset=1, limit=1000, file has 1121 lines ✅ Yes — 1000 shown, 121 remaining
offset=500, limit=500, file has 1121 lines ✅ Yes — 500 shown, 122 remaining
offset=1, limit=1, file has 10 lines ✅ Yes — 1 shown, 9 remaining
offset=1, limit=1000, file has 500 lines (full) ❌ No — returned >= totalLines
byte-truncated (result.truncated === true), lines also truncated ❌ No — byte truncation is the primary concern; (truncated) flag already covers it. The hint would be misleading (suggesting offset pagination can reach bytes already lost to maxBytes cap)
byte-truncated=false but isFullFileReadGrant returns false (pre-#730, offset≠1) ✅ Yes — hint shown even without edit grant. Read pagination help is always useful

What stays the same

Merge-order dependency

#730 must land first. Without #730, isFullFileReadGrant still has the returned >= totalLines check, so the hint would appear alongside a (truncated) flag even for offset=1 full-file byte reads. The hint itself is still correct, but the dual signal (hint + truncated flag) would be confusing. Merging #730 first keeps the two fixes cleanly separated.

Plan layers

Layer Changes
Vercel backend lib/agent/tools.ts — ~6 lines in read_file execute body (lines 500–504)
Tests lib/agent/tools.test.ts — +5 test cases
Docs docs/sandbox.md — note the metadata hint in read_file tool description row

Test plan (updated — hint lives in execute body, testable via read_file tool execute)

# Test Expected
1 offset=1, limit=50, file=100 lines Hint: "use offset=51 to read the remaining 50 lines"
2 offset=1, limit=50, file=50 lines (full) No hint
3 offset=1, limit=50, file=51 lines Hint: "remaining 1 line" (singular)
4 byte-truncated=true, lines truncated No hint — byteTruncated gate blocks it
5 offset=500, limit=100, file=700 lines Hint: "use offset=600 to read the remaining 101 lines"

Cloud ops

N/A — no Production mutate.

Merge-gate residual risk

Low. The hint is a purely additive metadata line appended to the read_file output body — no schema changes, no protocol changes, no daemon changes. The hint format is deterministic and parseable by the model but never relied upon for correctness (the model can always read_file with any offset regardless of the hint). The byteTruncated gate prevents misleading hints when the maxBytes cap is the actual limiting factor.

Ref

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions