Conversation
Introduce `sandbox offload` and `sandbox matrix` subcommands for one‑shot offload and parallel fork workflows. Add `sandbox self` for in‑sandbox signaling. Implement guards, retry logic, and improved error handling for lifecycle, connection setup, and disk operations.
fork --count was checked after resolving the source ref, so an invalid count still paid for an API lookup before failing. Move the check to the top of runFork. Unknown command names (root or nested) printed a suggestion but exited 0: urfave's CommandNotFoundFunc has no return value, so ShowCommandHelp always returns nil after calling it. Root's own custom Action also never reached that path at all, since urfave only falls back to CommandNotFoundFunc through its default help Action. Detect the unresolved name directly in each command's own Action instead, and return a real error so app.Run (and main.go's exit code) see the failure. Claude-Session: https://claude.ai/code/session_019J9PwFePTzAhpPs1TY9ZGq
Go's stdlib flag parsing stops at the first positional argument, so a command like `fork <sandbox> --count 2` left `--count` unparsed and defaulted to 1. Added `forkCountFlag` that falls back to scanning raw os.Args, restoring the intended count.
The computer-use API has shipped in all three SDKs but never reached the CLI, so the only way to drive a sandbox desktop from a shell was to talk to the REST API by hand. The Claude Code plugin does exactly that in its cos driver, which is the one place it bypasses this binary — and the other integrations, which only shell out, cannot do it at all. `sandbox desktop` turns on ingress, waits for the desktop stack to come up, and prints a noVNC link. `sandbox computer` drives that desktop: screenshot, screen, cursor, windows, move, click, type, key, open, plus a hidden raw escape hatch for the routes not wrapped here. Three things carried over from cos because each one costs a debugging session to rediscover: - The desktop stack starts after the sandbox reports running, and nothing upstream polls for it, so every caller writes the wait itself. - fc answers 409 desktop_unavailable both while the desktop is booting and when an action fails on a live desktop. ComputerError.Retryable encodes which codes a waiter should keep trying, so the readiness wait neither gives up on a booting desktop nor spins on a permanent failure. - Driving a non-desktop image fails early with the fix, rather than a bare 501 from the first call. Flags are re-scanned by hand because urfave stops parsing them at the first positional argument, so `computer screenshot my-box --out shot.png` otherwise writes to the default path without reporting anything wrong — the same workaround `sandbox edit` makes for --ingress. `--out` has no short alias: -o is taken by the global output-format flag. Verified against a live desktop:1 sandbox: link minted, Chrome driven to a page by chord, typing and Return, pointer landed on the target link, and every error path checked.
`sandbox desktop my-box --screen screen-1` dropped --screen, and --wait with it, because urfave stops parsing flags at the first positional. The computer subcommands already re-scanned their arguments by hand; desktop read its flags straight off the context and so kept the bug. Route desktop through the same parser and teach it --wait. A duration it cannot parse now keeps the declared default instead of zeroing, which would have turned the readiness wait into a single attempt. Tests cover both flag positions, the equals form, the short alias, and operands surviving around a flag.
CI runs golangci-lint, which this was not developed against. Two classes: a type assertion on an error in the test, which breaks once anything wraps it, and three `err` shadows in runDesktop. Assert with errors.As, and assign to the existing err instead of redeclaring it.
…nto feat/sandbox-compositions-computer
Combines #84 (offload, matrix, self, fork --count) with #94 (desktop, computer) on current main. Conflicts were against #86's running-status preflight: - fork.go: keep #84's forkN, using main's typed SandboxStatus consts - push.go: running preflight first, then #84's disk-mount check; rename err to runErr to satisfy govet shadow, matching pull.go
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.
Supersedes #84 and #94, merged onto current main. The plugin side (createos-plugins#70) needs both, so they ship as one PR.
From #84
sandbox offload <dir> -- <cmd>: stage, run, fetch, destroysandbox matrix <dir>: build one golden sandbox, run--prepareonce, fork it N times, run one job per clonesandbox self pause|deletesandbox fork --count Npause/resumenow send a{}body; fork waits for pause to finishFrom #94
sandbox desktopandsandbox computer: screenshot, click, type, key, open, windowsConflict resolution against #86 (running-status preflight)
fork.go: keeps feat(sandbox): add offload, matrix and self commands with safety guards #84'sforkN, using main'sapi.SandboxStatus*constantspush.go: checks that the sandbox is running first, then feat(sandbox): add offload, matrix and self commands with safety guards #84's disk-mount guardCleanup (
/simplify)desktopusesedit's sandbox picker instead of its own copy.api.SandboxStatus*constants.Verification
go build ./...,go test -count=1 ./...andgolangci-lint runpass.Known gap (already in #84)
sandbox pull <box> <remote> -streams to stdout and returns before the disk-mount guard runs, so a piped pull from a mounted path skips that check.