Skip to content

fix(collect): read a large file in bounded chunks so an exec stream can't drop output - #337

Closed
dmchoiboi wants to merge 1 commit into
mainfrom
dmchoi/_claude/arp-collect-chunked
Closed

dmchoiboi wants to merge 1 commit into
mainfrom
dmchoi/_claude/arp-collect-chunked

Conversation

@dmchoiboi

@dmchoiboi dmchoiboi commented Oct 26, 2026 •

Copy link
Copy Markdown

What and why

collect_artifacts._collect_file streamed base64 < file through one exec. Some sandbox exec channels buffer only a bounded amount of output per stream (about 16 MiB on one remote VM provider) and silently drop the oldest events when the client lags, exiting 0. A big deliverable therefore arrives corrupt or cut off (FDE-2537; the customer saw collect integrity check failed … decoded 12823548 bytes but source is 15400836).

Repro on a remote VM sandbox with that limit, 2 trials per size, before this change:

File size Result
1 MB intact
8 MB intact
15.4 MB corrupt in both trials: Invalid base64 … 1 more than a multiple of 4, Incorrect padding
40 MB cut off at about 20 MB of base64

Exit code was 0 in every case.

Now _collect_file sizes the file first. A file over 4 MiB is read in ranges of at most 4 MiB (tail -c +N | head -c M | base64, one exec each, ~5.4 MB of base64, well under the buffer), each decoded on its own into the temp file. A chunk that comes back short raises. Files at or under 4 MiB keep the single exec, so they pay no extra round-trips. The final size check and the _exec_args wrapping (sudo for a VM host, docker exec -u 0 for a container) are unchanged. It applies to every provider.

The ranged read follows the existing CUA pattern (_controller_stream_to_file); I did not merge the two because that one goes through cua_bash, this one through sandbox.exec.

Needs a release to reach the customer (they run 0.9.1351); the PR carries no bump-version label.

How it was tested

  • New tst/unit/task_step/test_collect_chunked.py against a fake sandbox that interprets the ranged command: multi-chunk reassembly is byte-exact (sha256), an exact multiple of the chunk size reads no extra chunk, a small file takes one unranged exec, a 0-byte file collects as empty, a short chunk raises, and a file that grows during collection fails the integrity check (added after review: the check sizes the file again after reading). Written first; 3 of the 5 failed on the old code for the right reason.
  • make unit-test: 10576 passed, 7 skipped (before the follow-up commit; tst/unit/task_step 1980 passed after it, and CI unit is green on the final commit). ruff check .: clean.
  • Live, on a staging VM (quay.io/containerdisks/ubuntu:22.04), _collect_file called against the real sandbox exec API and a fake store, on the sudo VM-host path:
File Collected sha256 vs the VM's
15,400,836 B random 15,400,836 B in 1.7 s match
40,000,000 B random 40,000,000 B in 5.3 s match

The sandbox was deleted and GET on it returns 404.

Not run locally: make int-test-*, installer-test, clean-install-test (CI covers them).

🤖 Generated with Claude Code

RetriggerView in GreptileConfidence Score: 5/5

The PR appears safe to merge; no new blocking issues were found.

Summary

Large files are collected in ranges of at most 4 MiB to avoid dropped output.

  • Large files reach artifact storage through bounded sandbox reads.

Reviews (3) · Last reviewed commit: "fix(collect): read a large file in bound..." · Reviewed by Greptile

@dmchoiboi
dmchoiboi requested a review from a team as a code owner October 26, 2026 19:45
Comment thread src/agent_env/task_step/task_steps/collect_artifacts.py
…an't drop output

collect_artifacts._collect_file streamed `base64 < file` through one exec. A sandbox exec
channel can buffer a bounded amount of output and silently drop the oldest events when the
reader lags, with exit 0, so a file near or over that size arrived corrupt or cut off. A file
larger than 4 MiB is now sized first and read in ranges of at most 4 MiB
(`tail -c +N | head -c M | base64`, one exec each), each decoded on its own; a chunk that comes
back short raises. Files at or under 4 MiB keep the single exec. The sudo / `docker exec -u 0`
wrapping is unchanged, and the integrity check sizes the file again after reading, so a file
that grew mid-collection is rejected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@dmchoiboi
dmchoiboi force-pushed the dmchoi/_claude/arp-collect-chunked branch from 0bd6bca to 3b9c7af Compare October 26, 2026 22:07
@dmchoiboi

Copy link
Copy Markdown
Author

Closing: not needed as a standalone change.

@dmchoiboi dmchoiboi closed this Oct 26, 2026
@dmchoiboi
dmchoiboi deleted the dmchoi/_claude/arp-collect-chunked branch October 26, 2026 22:13
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