Status: IMPLEMENTED
Target
lib/agent/tools.ts — read_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
Status: IMPLEMENTED
Target
lib/agent/tools.ts—read_filetool result rendering (lines 480–505).Current baseline (verified against live code at
9d133fb)formatLineWindowreturns{ body, returned, totalLines }— no metadatatools.ts:114-128read_fileexecute already hasresult.truncated(byte-level)tools.ts:482read_fileexecute hasoffset+limit+window.returned+window.totalLinesavailabletools.ts:478-487read_fileoutput builds header +window.bodyat the endtools.ts:502-504isFullFileReadGrantstill hasreturned >= totalLineson maintools.ts:138Root cause
When
read_fileclips 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_fileexecute body, not insideformatLineWindow) when the line window clips the file AND the read was not byte-truncated:Logic (lives in
read_fileexecute body afterformatLineWindow, lines ~502–504)Why in the execute body, not
formatLineWindow:formatLineWindow(content, offset, limit)doesn't receivebyteTruncated. The byte-truncation gate must be checked before appending the hint. Putting the logic in the execute body (whereresult.truncatedis already available) keepsformatLineWindow's signature unchanged and the gate correct.Edge cases
returned >= totalLinesresult.truncated === true), lines also truncated(truncated)flag already covers it. The hint would be misleading (suggesting offset pagination can reach bytes already lost to maxBytes cap)isFullFileReadGrantreturns false (pre-#730, offset≠1)What stays the same
formatLineWindowsignature — unchanged (hint appended in execute body, not inside the function)isFullFileReadGrant— unchanged (fixed by fix(tools): decouple read-before-edit grant from line-window limit (#729) #730, no further changes in this plan)(truncated)byte flag — unchangedREAD_FILE_DEFAULT_LIMIT= 1000 — unchangedMerge-order dependency
#730 must land first. Without #730,
isFullFileReadGrantstill has thereturned >= totalLinescheck, 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
lib/agent/tools.ts— ~6 lines inread_fileexecute body (lines 500–504)lib/agent/tools.test.ts— +5 test casesdocs/sandbox.md— note the metadata hint inread_filetool description rowTest plan (updated — hint lives in execute body, testable via
read_filetool execute)byteTruncatedgate blocks itCloud 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_filewith any offset regardless of the hint). ThebyteTruncatedgate prevents misleading hints when the maxBytes cap is the actual limiting factor.Ref