From d6a5c72ac7db163cd66d290f5f19484976ab70b1 Mon Sep 17 00:00:00 2001 From: claude Date: Tue, 23 Jun 2026 09:15:17 +0000 Subject: [PATCH] fix(smolmachines): pipe tar stdout via PIPE not file fd smolvm machine exec requires stdout to be a pipe, not a regular file descriptor. Passing stdout=file caused smolvm to return non-zero with no stderr (the error was silently swallowed or went to the regular-file fd instead of reaching us). Switch _snapshot_running_vm to a new _exec_tar_to_file helper that uses Popen with stdout=PIPE and streams the tar to disk via shutil.copyfileobj. A background thread drains stderr concurrently to prevent deadlock when the stderr pipe buffer fills while we are writing stdout data. --- bot_bottle/backend/smolmachines/freezer.py | 73 +++++++++++++++------- 1 file changed, 50 insertions(+), 23 deletions(-) diff --git a/bot_bottle/backend/smolmachines/freezer.py b/bot_bottle/backend/smolmachines/freezer.py index 561a4d4..582f668 100644 --- a/bot_bottle/backend/smolmachines/freezer.py +++ b/bot_bottle/backend/smolmachines/freezer.py @@ -9,8 +9,10 @@ running throughout.""" from __future__ import annotations +import shutil import subprocess import tempfile +import threading from pathlib import Path from .. import ActiveAgent @@ -56,29 +58,7 @@ def _snapshot_running_vm(machine: str, image_ref: str, binary: Path) -> None: rootfs_tar = tmp_path / "rootfs.tar" dockerfile = tmp_path / "Dockerfile" - with open(rootfs_tar, "wb") as tar_out: - result = subprocess.run( - [ - "smolvm", "machine", "exec", - "--name", machine, "--", - "tar", "--create", - "--exclude=./proc", - "--exclude=./sys", - "--exclude=./dev", - "--exclude=./run", - "--file=-", - "--directory=/", - ".", - ], - stdout=tar_out, - stderr=subprocess.PIPE, - check=False, - ) - if result.returncode != 0: - die( - f"smolvm exec tar {machine!r} failed: " - f"{(result.stderr or b'').decode().strip() or ''}" - ) + _exec_tar_to_file(machine, rootfs_tar) dockerfile.write_text( "FROM scratch\n" @@ -99,3 +79,50 @@ def _snapshot_running_vm(machine: str, image_ref: str, binary: Path) -> None: pack_create(pack_ref, binary) finally: image_tarball.unlink(missing_ok=True) + + +def _exec_tar_to_file(machine: str, dest: Path) -> None: + """Stream the VM root filesystem as a tar archive to `dest`. + + smolvm machine exec requires stdout to be a pipe, not a regular file. + We use Popen with stdout=PIPE and drain stderr in a background thread + to avoid deadlock if either buffer fills while we're writing the other.""" + proc = subprocess.Popen( + [ + "smolvm", "machine", "exec", + "--name", machine, "--", + "tar", "--create", + "--exclude=./proc", + "--exclude=./sys", + "--exclude=./dev", + "--exclude=./run", + "--file=-", + "--directory=/", + ".", + ], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + + stderr_chunks: list[bytes] = [] + + def _drain_stderr() -> None: + assert proc.stderr is not None + stderr_chunks.append(proc.stderr.read()) + + t = threading.Thread(target=_drain_stderr, daemon=True) + t.start() + + assert proc.stdout is not None + with open(dest, "wb") as out: + shutil.copyfileobj(proc.stdout, out, length=65536) + + t.join() + returncode = proc.wait() + stderr = b"".join(stderr_chunks).decode(errors="replace").strip() + + if returncode != 0: + die( + f"smolvm exec tar {machine!r} failed: " + f"{stderr or ''}" + )