Skip to content

bug(session): apply_patch full-file result metadata exhausts the 64 MiB transcript during a live turn #4566

Description

@moonbees01

Summary

On GJC 0.13.2, a long but otherwise healthy managed session can abort during message_end when its transcript reaches the 64 MiB per-file limit. The dominant growth is not user/model prose or rendered diffs: every successful apply_patch tool result persists the complete pre-edit and post-edit file contents in message.details.oldText and message.details.newText.

Repeated small edits to one large source file therefore add roughly 2 × file_size to the JSONL on every patch. Once the next tool result no longer fits, managed append raises content_too_large; AgentSession aborts the active turn, and the just-applied source mutation may exist without its tool result or subsequent verification being durably recorded.

This is distinct from:

I found no existing issue covering live append amplification from edit-result metadata.

Observed evidence

A real session ended with:

Session persistence error
SessionAppendPersistenceError: content_too_large
  at appendManagedFileStreamingSync (.../managed-session-storage.ts:3053)
  at #appendManagedRecordsSync (.../session-manager.ts:15630)

Sanitized measurements from that transcript:

  • transcript at failure: 66,553,990 bytes (63.4708 MiB)
  • per-file limit: 67,108,864 bytes (64 MiB)
  • remaining capacity before the failed append: 554,874 bytes
  • entries: 1,387
  • apply_patch tool results: 180
  • bytes attributable to those results: about 57.05 MiB (about 90% of the transcript)
  • assistant entries: about 3.77 MiB
  • all other tool-result families combined: about 2.63 MiB

The edited file was roughly 395–397 KiB. Individual small apply_patch results were about 0.76 MiB because their persisted details contained both full-file snapshots. The final append exceeded the remaining 0.53 MiB and failed deterministically.

The provider request itself had not failed for context overflow; the logged terminal cause was local persistence content_too_large.

Current code path

In tagged 0.13.2 and current dev:

  1. packages/coding-agent/src/edit/modes/patch.ts returns full oldText and newText in EditToolDetails.
  2. packages/coding-agent/src/edit/index.ts retains those fields in single-file and aggregated apply_patch results.
  3. packages/coding-agent/src/session/session-manager.ts persists the complete toolResult to the managed JSONL.
  4. packages/coding-agent/src/session/internal/managed-session-storage.ts rejects append when predecessor size plus appended bytes exceeds MANAGED_ARTIFACT_MAX_FILE_BYTES (64 MiB).
  5. packages/coding-agent/src/session/agent-session.ts treats a normal tool-result SessionAppendPersistenceError as fatal and aborts the agent. Only todo_write has specialized reconciliation handling.

Compaction does not reliably prevent this boundary: the observed session compacted once, then accumulated another 34.66 MiB after the compaction, primarily through repeated edit results. Cold spill also preserves data elsewhere and does not change the hard per-file append contract by itself.

Minimal reproduction

A synthetic test does not need private session content:

  1. Create a managed session and a source file around 400 KiB.
  2. Apply many one-line apply_patch updates to that same file.
  3. Inspect each persisted tool result and confirm it contains the complete old and new file bodies.
  4. Continue until the transcript is just below 64 MiB.
  5. Apply one more small patch.

Expected current result: the file mutation succeeds, then persistence of the tool result raises content_too_large and aborts the turn.

The same behavior can be reproduced faster with a larger synthetic file while remaining below the edit input/output guards.

Expected behavior

A small edit should have persistence cost proportional to its bounded result evidence, not twice the entire target file. Reaching a storage boundary must not leave an applied mutation followed by an unrecorded tool result and silently skipped verification.

Suggested fix boundary

The exact representation is a maintainer decision, but a complete fix likely needs both parts:

  1. Bound persisted edit-result details. Keep the rendered diff, path, operation, diagnostics, and content identities/digests needed for replay. Do not inline complete oldText/newText into every durable tool result. If full snapshots are required for UI/fidelity, place them in a bounded content-addressed artifact and persist references, or strip them before session persistence after their process-local consumer is finished.
  2. Handle near-limit live append explicitly. Preflight the serialized record against remaining transcript capacity and perform a typed recovery (for example a fresh linked session/handoff or a recoverable terminal receipt) before mutation where possible. At minimum, surface a structured error that states the source mutation may already have committed and provides an exact recovery path; do not continue emitting repeated message_end persistence warnings.

Simply raising the 64 MiB constant would delay the same amplification and increase resume/memory risk.

Acceptance criteria

  • A regression test repeatedly applies small patches to a large synthetic file and proves durable transcript growth is bounded independently of full file size.
  • Persisted edit results retain enough bounded evidence for rendering, diagnostics, replay/fidelity, and source-change accounting without duplicating both complete file bodies per edit.
  • A synthetic near-64-MiB live append cannot terminate with an unclassified SessionAppendPersistenceError after a source mutation.
  • The recovery result states whether the edit committed and how to continue safely.
  • Existing resume: oversized session (~64 MiB) hard-fails with too large and no graceful fallback #3851 oversized-resume behavior and sub-limit sessions remain unchanged.

Environment

  • GJC 0.13.2
  • macOS arm64
  • Bun 1.3.14

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions