Skip to main content

max / makenotwork

bento: stream AgentRpc::pull to disk (Run 3 performance) The agent's /pull streams in 64 KiB chunks, but the client read the whole response with resp.bytes() before writing — a hundreds-of-MB signed .app/.dmg fully materialized in the daemon heap. Stream resp.bytes_stream() straight to the file instead, matching the server. ops-exec 39 tests; clippy -D clean. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-06-23 02:48 UTC
Commit: cb10468a089a193e965c347933ef5180978acf0b
Parent: 5e7165c
1 file changed, +10 insertions, -2 deletions
@@ -145,11 +145,19 @@ impl Executor for AgentRpc {
145 145 .context("GET /pull")?
146 146 .error_for_status()
147 147 .context("agent /pull status")?;
148 - let bytes = resp.bytes().await.context("reading /pull body")?;
148 + // Stream the body straight to disk in chunks — a signed .app/.dmg can be
149 + // hundreds of MB, so never buffer the whole artifact in the daemon's heap
150 + // (the agent's /pull already streams; this is the matching client half).
149 151 if let Some(parent) = local.parent() {
150 152 tokio::fs::create_dir_all(parent).await.ok();
151 153 }
152 - tokio::fs::write(local, &bytes).await.context("writing pulled artifact")?;
154 + let mut file = tokio::fs::File::create(local).await.context("creating pulled artifact")?;
155 + let mut stream = resp.bytes_stream();
156 + while let Some(chunk) = stream.next().await {
157 + let chunk = chunk.context("reading /pull body")?;
158 + tokio::io::AsyncWriteExt::write_all(&mut file, &chunk).await.context("writing pulled artifact")?;
159 + }
160 + tokio::io::AsyncWriteExt::flush(&mut file).await.context("flushing pulled artifact")?;
153 161 Ok(())
154 162 }
155 163