[BUG] Read tool double-persists binary files in session JSONL (toolUseResult.file.base64 + document content block)

Resolved 💬 2 comments Opened May 23, 2026 by Mike-E-Log Closed Jun 22, 2026

Summary

The Read tool persists every binary file (PDF, image) twice in the session JSONL: once as toolUseResult.file.base64 and once as a document/image content block in the next user-role message. The two copies are byte-identical. A single 1.6 MB PDF read costs ~4.4 MB of session-file disk and replays in every subsequent assistant turn's request payload.

Environment

  • Claude Code version: 2.1.149
  • Platform: Windows 11 (win32) — also seen reported in other-OS contexts via #16251
  • Python: 3.13.13
  • Project: contains PDF reads (vet records, ~1.6 MB each)

Steps to Reproduce

  1. In any project, use the Read tool to load a 1–2 MB PDF: Read("/path/to/file.pdf").
  2. Locate the session JSONL under ~/.claude/projects/<encoded-cwd>/<sessionId>.jsonl.
  3. Inspect the line containing that tool result and the line immediately after.
import json
with open("<sessionId>.jsonl", "rb") as f:
    lines = f.readlines()

# Line N: the tool result for the Read call
d_n = json.loads(lines[N-1])
b64_a = d_n["toolUseResult"]["file"]["base64"]

# Line N+1: the next user message Claude Code emits, containing the document block
d_np1 = json.loads(lines[N])
b64_b = d_np1["message"]["content"][0]["source"]["data"]

print(b64_a == b64_b)       # True
print(len(b64_a), len(b64_b))  # identical, ~2.18 MB each for a 1.6 MB PDF

Actual Behavior

Measured across one author's project (help-cats) sessions:

| session | size | binary reads | shadow copy (toolUseResult.file.base64 only) |
|----------------------------------------------|-------|---------------------|-----------------------------------------------|
| c5704fcc-…jsonl | 29.3 MB | 5 PDFs + 23 images | 9.6 MB (33%) |
| 9e7ee380-…jsonl | 6.3 MB | 13 images | 1.7 MB (26%) |
| c74123b7-…jsonl | 3.7 MB | 2 PDFs | 0.1 MB (3%) |

The document/image content-block copies add the same amount again (verified byte-identical for the c5704fcc case: lines 454/455 hold the same 2.18 MB base64 for tora tran.pdf). So for that session ~20 MB of 30 MB is pure duplication of binary payloads.

Expected Behavior

The session JSONL stores each binary file's bytes at most once per tool call. Possible designs:

  • Drop toolUseResult.file.base64 entirely if the next emitted user message already contains the same content block (the API-bound copy is the load-bearing one).
  • Store the binary out-of-line in a sidecar file under the project directory, and reference it by relative path in both records.
  • Strip toolUseResult.file.base64 on session-file write but keep it in memory for the in-process inspector / TUI.

Impact

  • Disk: sessions inflate by ~2× the size of every binary file read. With 1–2 MB PDFs and multiple sessions, projects accumulate hundreds of MB quickly.
  • Token cost on resume / continue: the full duplicated payload is replayed to the model on every subsequent turn (the content-block copy is in the conversation history), inflating prompt size for the rest of the session.
  • UX: reading past sessions in /resume or scrolling the JSONL shows the same long opaque base64 string twice for every binary read — the user-visible "repeating texts."
  • Likely upstream cause of #16251 (/resume crashing on large tool results, closed): the duplication makes individual JSONL lines exceed parser limits roughly twice as fast as a single-copy design would.

Workaround

Pre-extract PDF text once with PyMuPDF and have Claude Read the .txt sidecar instead of the .pdf. Drops per-read cost from ~4.4 MB to a few KB. Doesn't help users who genuinely need the visual layout (forms, scans), but works for any text-bearing record.

Related

  • #16251 (closed) — /resume command crashing on large tool results.

View original on GitHub ↗

This issue has 2 comments on GitHub. Read the full discussion on GitHub ↗