Skip to content

feat(computer): a boot stops when its sandbox does and says what it last wrote - #629

Merged
earakely-scale merged 5 commits into
mainfrom
edgararakelyan/disk-boot-says-why
Nov 12, 2026
Merged

earakely-scale merged 5 commits into
mainfrom
edgararakelyan/disk-boot-says-why

Conversation

@earakely-scale

@earakely-scale earakely-scale commented Nov 11, 2026 •

Copy link
Copy Markdown
Collaborator

Part of FD-5171.

What

A computer's boot no longer waits out its readiness timeout (180 s by default) once the sandbox has stopped running the computer. Within seconds of the computer's container, or the launcher booting its disk, exiting, the boot fails with ComputerStopped (a ComputerFailed). The error names the exit status, and the signal for a kill, then what the sandbox last wrote: the guest init's agentenv-init: lines, the launcher's launch: lines and the last few lines of all. A boot that does time out, as a hung guest does, now ends its TimeoutError with those lines too.

Before:

the computer at http://127.0.0.1:27350 wasn't ready within 180 s (nothing answered)

After, a disk whose image's user isn't in its /etc/passwd, after 7 s on local Docker with KVM and 10 s on modal_vm:

the computer at http://127.0.0.1:27350 stopped before it was ready, exiting with 0; its sandbox last wrote:
  BdsDxe: starting Boot0001 "UEFI Misc Device" from PciRoot(0x0)/Pci(0x4,0x0)
  agentenv-init: the image's user ghost isn't in /etc/passwd
  [    2.087206] reboot: Power down
  2026/11/11 22:25:48 socat[21] E read(5, 0x55a2dd327000, 8192): Connection reset by peer
  2026/11/11 22:25:48 socat[12] W exiting on signal 15

A launcher that can't forward the driver's port, after 0.4 s:

the computer at http://127.0.0.1:27351 stopped before it was ready, exiting with 2; its sandbox last wrote:
  launch: port 60000 can't be forwarded: QEMU's forward behind its proxy would take port 70000, which is above 65535

How

  • Sandbox gets two optional methods, exit_status() and output_tail(lines). Their defaults, None and [], keep plugin providers working, and those still get today's timeout.
  • LocalSandbox answers them for the container it was made to run (a computer's, or the launcher booting a disk), from docker container inspect and docker logs --tail.
  • ModalSandbox and ModalVmSandbox answer from poll(). Once the process has exited, they read its stdout and stderr once and keep them.
  • boot.ready takes the sandbox (boot() and a reset pass it), asks every 2 s whether the sandbox still runs the computer, and stops as soon as it doesn't. said_why picks the lines that go into the error.
  • sandbox_providers/AGENTS.md documents the two methods for provider authors.

Verified

  • Unit:
    • New: tst/unit/env/envs/test_computer_boot_stops.py and tst/unit/providers/sandbox_providers/sandbox_exit_test.py.
    • The existing boot, reset and disk tests now check that the sandbox is passed.
    • The computer, sandbox-provider and env suites pass locally.
  • Local Docker with KVM (EC2 c8i.4xlarge):
    • tst/integration/computer/launcher_test.py: the guest-init refusal and the port refusal above, and the existing burst-and-reset test.
    • disk_boot_test.py: the full deploy, probe and reset of the desktop's disk, which runs the reset path that now passes the sandbox.
  • Local Docker, fast tier: tst/integration/computer/container_stopped_test.py. A computer's container that exits with 4 fails its boot within seconds, naming its status and its last lines, and its container is removed.
  • modal_vm: tst/integration/computer/disk_boot_failure_modal_vm_test.py covers both disk cases under nested virtualization on Modal. It runs by hand, as the other modal_vm tests do.

Notes for review

  • Docker keeps no order between a container's stdout and stderr lines, and its log timestamps don't either. The launcher's launch: lines and the guest's console can come in either order. said_why keeps the reason lines wherever they fall.
  • The last lines on a disk often include the launcher's socat proxies shutting down (above). That's noise, but quieting them means changing the launcher and republishing its image, so I left it.
  • The lines come from the computer's own output. The guest init and the launcher print no settings or tokens, but a base or app that prints a secret to its output would now see it in a failed boot's error.
  • Modal hands a sandbox's output only once its process has exited, so on Modal a boot that times out adds no lines. Local Docker adds them either way.

🤖 Generated with Claude Code

RetriggerView in GreptileConfidence Score: 5/5

The PR appears safe to merge; the previous output-loss finding is fixed and no new actionable issues were found.

Summary

Computer boots now detect stopped sandboxes and include recent output in failure messages.

  • Computer boots stop waiting when their sandbox process exits.
  • Integration tests serialize builds that use the same image tag.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Boot or reset] --> B[Wait for computer readiness]
    B --> C{Computer ready?}
    C -->|Yes| D[Return computer card]
    C -->|No| E{Sandbox stopped?}
    E -->|Yes| F[Read recent output]
    F --> G[Raise ComputerStopped]
    E -->|No| H{Readiness timeout?}
    H -->|No| B
    H -->|Yes| I[Read available output]
    I --> J[Raise TimeoutError]
Loading

Reviews (5) · Last reviewed commit: "fix(computer): a Modal sandbox's ended o..." · Reviewed by Greptile

…ast wrote

A computer's boot asks the sandbox running it whether its container, or the
launcher booting its disk, still runs. Within seconds of it exiting, the boot
fails with ComputerStopped, naming the exit status and the sandbox's last lines:
the guest init's agentenv-init: and the launcher's launch: lines, then the last
few. A boot that times out ends its error with those lines too. Sandbox gains
optional exit_status() and output_tail(), defaulting to nothing, which local
Docker answers from its container and Modal from its sandbox's process.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@earakely-scale
earakely-scale requested a review from a team as a code owner November 11, 2026 23:32
earakely-scale and others added 2 commits November 11, 2026 15:46
…l of each stream

The output a failed boot reads of a Modal sandbox is read a chunk at a time,
keeping the last lines of its output and of its errors apart, and its tail
gives each stream at least half of the lines asked for, so a long log isn't
held whole and the guest's console isn't crowded out by the launcher's errors.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…628)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
earakely-scale and others added 2 commits November 11, 2026 18:56
… build of it

Two test workers composing the same computer, each in stores of its own,
build the same tag on one Docker daemon, and the second to finish fails
with "image ... already exists". The computer tests' builds of a tag now
take turns across workers, as the bootstrap images' builds do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…treams

When one stream's read failed, gather returned at once and the other's
later lines, the guest's reason among them, never reached the boot's
error. Both reads now finish within the timeout, and each failure is
logged on its own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@earakely-scale
earakely-scale merged commit 4c1f405 into main Nov 12, 2026
34 checks passed
@earakely-scale
earakely-scale deleted the edgararakelyan/disk-boot-says-why branch November 12, 2026 15:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant