Skip to content

streams: cap JSONL line size and transcript batch bytes - #2245

Draft
scouredimage wants to merge 1 commit into
git-ai-project:mainfrom
scouredimage:fix/2244-1-transcript-byte-caps
Draft

streams: cap JSONL line size and transcript batch bytes#2245
scouredimage wants to merge 1 commit into
git-ai-project:mainfrom
scouredimage:fix/2244-1-transcript-byte-caps

Conversation

@scouredimage

@scouredimage scouredimage commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Part 1/7 of the #2244 fix stack (transcript-sweep memory balloons -> watchdog abort loop -> blocked traced git).

Transcript batches were bounded by event count only (1000), and read_jsonl_line buffered lines of unbounded length; a transcript whose events embed file contents can put hundreds of MB into one batch, which downstream redaction + metrics conversion amplify several times over.

  • read_jsonl_line: 8 MiB per-line cap, byte-based (read_until) so a cap that slices a multi-byte character classifies as the new JsonlLineState::Oversized instead of InvalidData (which would wedge the stream at a fixed watermark). Oversized lines are skipped without buffering; non-UTF-8 within the cap keeps the InvalidData contract. Unit tests cover the multi-byte boundary.
  • All JSONL byte-offset parsers (claude, codex, copilot, cursor, droid, gemini, pi, windsurf): batches stop early at 8 MiB of raw JSON.

Not covered: amp / continue_cli / opencode parse whole files into a DOM and need separate treatment (follow-up noted in #2244).

Stack

  1. streams: cap JSONL line size and transcript batch bytes #2245 - streams: cap JSONL line size and transcript batch bytes
  2. daemon: serialize transcript metric events incrementally #2246 - daemon: serialize transcript metric events incrementally
  3. daemon: cap control/trace socket line size #2247 - daemon: cap control/trace socket line size
  4. daemon: advance stream watermark before batch processing #2248 - daemon: advance stream watermark before batch processing
  5. daemon: memory watchdog decides on current RSS, not lifetime peak #2249 - daemon: memory watchdog decides on current RSS, not lifetime peak
  6. daemon: never defer self-restart while processing is stalled #2250 - daemon: never defer self-restart while processing is stalled
  7. streams: cap the initial backfill of first-seen stream files #2251 - streams: cap the initial backfill of first-seen stream files

Each PR targets main; the incremental diff of part N is its last commit. Root-cause analysis, production evidence, and the repro live in #2244.

Verification

  • Deterministic repro from Daemon RSS balloons on large agent transcript sweeps; watchdog abort loops from watermark 0 and blocks traced git (v1.6.24) #2244 (315 MB synthetic Cursor transcript + one commit, 384 MB
    daemon memory limit): unpatched v1.6.24 aborts in ~1 s and re-aborts after respawn
    (watermark 0); with the full stack the daemon survives, peak RSS 76 MB vs 589 MB,
    zero memory emergencies, traced-git probes at 0.05-0.07 s throughout.
  • cargo fmt --check clean; cargo test --lib: 2,401 passed. Three failures
    (commands::upgrade::...pending_update, daemon::...conflict_resolution_note_read_errors...,
    git::authorship_traversal::...ai_touched_files...) reproduce identically on clean main
    in this environment - pre-existing, unrelated.

Transcript batches were bounded by event count only (1000), and
read_jsonl_line buffered lines of unbounded length. A transcript whose
events embed file contents (normal for agent tool results) could put
hundreds of MB into a single batch, which downstream redaction and
metrics conversion amplify several times over -- ballooning daemon RSS
past the memory watchdog within seconds (git-ai-project#2244).

- read_jsonl_line: cap a single line at MAX_JSONL_LINE_BYTES (8 MiB).
  The read is byte-based (read_until), NOT read_line: the cap can
  slice a multi-byte character, and read_line's UTF-8 validation would
  return InvalidData instead of classifying the line as Oversized --
  wedging the stream at a fixed watermark. Oversized lines are skipped
  without being buffered (skip_until); callers advance their watermark
  past them via the new JsonlLineState::Oversized state. Non-UTF-8
  content within the cap keeps read_line's InvalidData contract.
- All JSONL byte-offset stream parsers (claude, codex, copilot,
  cursor, droid, gemini, pi, windsurf): stop a batch early once
  MAX_BATCH_BYTES (8 MiB) of raw JSON has been accepted; remaining
  events arrive in later batches.

Not covered here: amp, continue_cli, and opencode parse whole files
into a DOM (serde_json::from_reader) and need a separate treatment --
called out in git-ai-project#2244 as follow-up.

Part 1/7 of the git-ai-project#2244 fix stack.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant