Repository navigation
fix(collect): stream large CUA artifacts and fail on dropped deliverables - #53
Merged
Merged
Conversation
…bles collect_artifacts read CUA desktop-VM files with a single cua_get_file call that buffers the whole file as one base64 body in one gateway response. A ~500MB deliverable blew past the step's 600s controller timeout (observed: run aeh-1790880504327, sintel_braided_assembly_v2.mp4, exactly 600s to the empty-error ReadTimeout), and because every per-file failure was caught and downgraded to a warning while the step still "succeeded" on the other files, the big file silently vanished from the result artifact. - Stream files larger than 16MB off the VM in 8MB byte ranges via cua_bash (`tail -c +N | head -c M | base64`, portable across the Ubuntu and macOS desktop VMs), decoding each range to the output — no single call moves the whole file. A short read fails loudly instead of uploading a partial artifact. - Distinguish absent from present-but-untransferable: a listed file that isn't on the VM stays a lenient skip, but a file that EXISTS and fails to read or upload now fails the step (retryable) instead of being dropped with a warning. Applies to the controller and the agent/VM-host/sandbox-container paths. - A size-probe (`wc -c` / `stat`) error is a hard failure, NOT a missing-file skip: -1 means genuinely absent only (stderr "No such file"); a probe that times out or otherwise errors raises so an existing deliverable is never silently dropped. The agent-path size check sits inside the guarded block so the provider still closes on failure. Unit tests cover chunked reassembly (with base64 wrapping), the short-read guard, absent -> FileNotFoundError vs present-but-failed -> fatal, zero-byte files, and size-probe errors -> fatal. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Addresses Greptile review on #53. P2: the chunked reader accumulated every decoded chunk in a bytearray and copied the whole file with bytes(buf) before writing it out, so a large collect still peaked at ~2x the file in memory. _controller_get_file now downloads to a temp file and returns its path — large files stream range-by-range straight to disk (bounded to one chunk), small files decode to the same file — and the controller loop uploads that file via _upload_file (replacing _write_and_upload). Memory no longer scales with file size. P1: _looks_absent matched "cannot stat", but a permission/IO error also says "cannot stat" — that would treat an unreadable existing file as missing and skip it. Match "No such file" only, so a non-missing stat/wc error stays a hard failure. Tests updated for the path-returning seam (stubs hand back a staged temp file); added an end-to-end check that a large file is streamed to disk and uploaded from its path with bytes intact. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
earakely-scale
approved these changes
Oct 3, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
collect_artifactsreads CUA desktop-VM files with a singlecua_get_filecall that buffers the whole file as one base64 body in one gateway/stepresponse. That can't carry a large file within one request, and every per-file failure was caught and downgraded to alogger.warningwhile the step still "succeeded" on the other files — so a large deliverable silently vanished from the result artifact.Confirmed in prod on run
aeh-1790880504327(file~/Desktop/sintel_braided_assembly_v2.mp4, ~500MB):Collecting 12 artifacts … via controller→Collected 11/12Failed to read …/sintel_braided_assembly_v2.mp4 via controller:logged exactly 600s after the read started — the_controller_calltimeout=600firing (httpxReadTimeout, empty string). A retry attempt died with a503from the sandbox/stepmid-transfer instead..mp4s (18–52MB each) collected fine in 1–9s — a size/transport ceiling, not a cap.Fix (contained, client-side)
cua_bash(tail -c +N | head -c M | base64, portable across the Ubuntu and macOS desktop VMs — no GNUdd iflag), decoding each range as it arrives. No single controller call moves the whole file. A short read (sum ≠wc -csize) fails loudly rather than uploading a truncated artifact. Files ≤16MB keep the existing single-shot path.FileNotFoundError); a file that exists but fails to read or upload now fails the step (retryable) instead of being dropped with only a warning. Applies to the controller and the agent / VM-host / sandbox-container paths.wc -c/statreturn-1only for a genuinely-absent file (stderr "No such file"); a probe that times out or otherwise errors raises, so an existing deliverable is never silently dropped by a flaky size check. The agent-path size check sits inside the guarded block so the provider still closes on failure.Tests
uv run --extra dev pytest tst/unit/task_step/→ 1385 passed. New/updated coverage intest_collect_artifacts.py: chunked reassembly (incl. base64 76-col wrapping), short-read → fatal, absent →FileNotFoundErrorvs present-but-failed → fatal, zero-byte file present, single-shot for small files, size-probe transport/wc/staterrors → fatal.Notes / trade-offs
wc -cround-trip per file (to size it before deciding single-shot vs chunked, and to tell absent from present-but-failed). Minor latency for correctness; mirrors the agent-container path, which already stats first._controller_get_file -> bytesseam; peak memory ≈ file size, fine for worker sizing.cua_get_filedefault 90s timeout lives inscaleapi/packages/agentenvhub-sdk; with chunking capping the single-shot path at 16MB it's non-blocking, so a 300s bump there is an optional separate follow-up.🤖 Generated with Claude Code
The PR is not ready to merge because an existing unreadable file can still be mistaken for a missing one.
Fix with agent prompt
Summary
This PR collects large CUA files in 8 MB ranges instead of sending each file in one gateway response. It also makes collection fail when an existing deliverable cannot be sized, read, or uploaded.
Diagram
%%{init: {'theme': 'neutral'}}%% flowchart LR A[Size file on VM] --> B{Over 16 MB?} B -- No --> C[Download in one call] B -- Yes --> D[Read chunks to temp file] C --> E[Upload temp file] D --> E E --> F[Remove temp file]Reviews (2) · Last reviewed commit: "fix(collect): stream chunks to disk and ..."