run
September 10, 2026 · View on GitHub
crabbox run syncs the current dirty checkout to a box, runs a command there,
streams the output back, and exits with the remote command's exit code. It is
the core verb: lease (or reuse) a machine, ship your code, run something, get
the result.
crabbox run --id swift-crab -- pnpm test:changed
crabbox run --class beast -- pnpm check
crabbox run --provider aws --class beast --market on-demand -- pnpm check
crabbox run --provider azure --class beast -- pnpm check
crabbox run --provider azure --arch arm64 --class fast -- go test ./...
crabbox run --tailscale -- pnpm check
crabbox run --id swift-crab --network tailscale -- pnpm test
crabbox run --browser -- google-chrome --headless --version
crabbox run --desktop --browser --shell 'echo "$DISPLAY"; "$BROWSER" --version'
crabbox run --id swift-crab --shell 'pnpm install --frozen-lockfile && pnpm test'
crabbox run --id swift-crab --script ./scripts/live-smoke.sh
crabbox run --id swift-crab --full-resync -- pnpm check:changed
crabbox run --label "update flow smoke" -- pnpm test:changed
crabbox run --slug update-flow-smoke -- pnpm test:changed
crabbox run --pond alpha --slug web -- pnpm test:integration
crabbox run --fresh-pr example-org/my-app#123 --script ./scripts/e2e-smoke.sh
crabbox run --id cbx_abcdef123456 --junit junit.xml -- go test ./...
crabbox run --provider ssh --target macos --static-host mac-studio.local -- xcodebuild test
crabbox run --provider ssh --target windows --windows-mode normal --static-host win-dev.local -- dotnet test
crabbox run --profile live-qa --preset qa-live --scenario login-regression --emit-proof /tmp/proof.md --stop-after success
crabbox run --pool example/app/main/linux --pool-compatibility-key linux-16-vcpu -- pnpm test
crabbox run --pool example/app/main/linux --pool-identity-file pool-identity.json -- pnpm test
The trailing command after -- is sent to the box verbatim as argv. Use
--shell to run it through the remote shell instead, for multi-statement
snippets, pipes, or shell expansion.
On Cloudflare Sandbox, Superserve, Crownest, Vercel Sandbox, Nomad, CodeSandbox,
OpenComputer, Docker Sandbox, Agent Sandbox, SmolVM, Upstash Box, Tensorlake,
and OpenSandbox,
quoted or interpolated profile arguments retain their literal meaning through
the delegated command transport. A value such as && does not become a shell
operator, and an executable named FOO=x is invoked rather than treated as an
environment assignment. Explicit --shell still selects shell source.
On POSIX SSH targets, --shell runs in a Bash login shell. Its startup and
logout files are part of that shell's behavior: for example, set -e plus a
failing ~/.bash_logout command can change an explicit exit 7 to exit 1.
Crabbox reports the shell's actual status. To load the login environment but
run a snippet in a separate non-login Bash, use argv explicitly:
crabbox run -- bash -c 'set -eu; ./scripts/test.sh'
This inner Bash inherits exported environment values, not unexported shell variables or functions from login startup files.
On POSIX and WSL2 SSH targets, private command staging does not change the remote caller's umask for user work. Commands keep the target shell's creation policy; Crabbox's staged scripts, input, and workspace-owner state remain private. Keeping or reusing a POSIX SSH lease also preserves the remote caller's SIGINT and SIGQUIT dispositions, including intentionally ignored signals.
POSIX workspace ownership uses flock, BSD lockf, or an atomic directory gate
when neither tool is available. Acquire, renewal, release, and foreground-child
registration share the same gate. The directory fallback never steals a gate
based on elapsed time: an interrupted helper may still have a writer in flight.
Stop and replace a managed lease if that gate remains ambiguous. Normal owner
expiry recovery still requires proof that the recorded foreground child exited.
Detached daemons should redirect stdin, stdout, and stderr explicitly (for
example, nohup sleep 600 </dev/null >daemon.log 2>&1 &) so they do not keep an
SSH command's streams open after its foreground shell exits.
On macOS, the command handoff also closes inherited internal descriptors left
by the system shell, preventing background processes from retaining its witness
pipe after the foreground command finishes.
Managed WSL2 commands, sync/copy, readiness checks, and workspace-owner helpers
run as the non-root crabbox distro user with HOME=/home/crabbox, passwordless
sudo, and a writable work root and caches. Node and npm remain on the default
PATH. Bootstrap alone runs as root; the Windows SSH account is unchanged.
Managed WSL2 leases disable WSL's distribution idle shutdown with
[general] instanceIdleTimeout=-1 in the bootstrap user's .wslconfig.
Detached Linux daemons can therefore outlive individual commands until the
lease is stopped. This does not change command deadlines, workspace ownership,
or lease expiration. Headless leases also disable WSLg; other WSL settings,
including the separate VM idle policy, are preserved.
Local Ctrl+C cancels the CLI's non-interactive SSH connection; it does not
guarantee that the remote foreground process has stopped. A retained lease can
therefore remain busy until that process exits. Crabbox preserves child
ownership and refuses conflicting reuse or evidence collection instead of
discarding the live process record. Stop a disposable lease with crabbox stop --provider <provider> --id <lease> when it is no longer needed. Static SSH hosts
are never destroyed by stop: finish or terminate the known remote workload on
that host before reusing its workspace. Do not delete owner records to bypass
the busy check.
Remote workspace root
Use CRABBOX_WORK_ROOT to change the portable base root for one run without
selecting a provider-specific flag:
CRABBOX_WORK_ROOT=/srv/crabbox \
crabbox run --provider "$PROVIDER" -- pnpm test
This changes the base root, not the command's exact working directory. A normal
SSH-backed run derives <root>/<lease>/<repository>, syncs the checkout there,
and uses that derived repository workspace as the command PWD. There is no
provider-neutral exact-chdir flag; provider adapters and hydration own their
workspace semantics.
An explicitly configured provider-specific work root or workdir takes
precedence over the generic root. If the lease has a valid GitHub Actions
hydration marker, its canonical WORKSPACE takes precedence over both roots
for sync and command execution. See Configuration
for configuration precedence and Sync
for path resolution.
Leasing model
If --id is omitted, Crabbox creates a fresh, non-kept lease and releases it
when the command exits. With --id it reuses an existing lease; --id accepts
either the stable cbx_... ID or the active friendly slug (see
identifiers). When --provider is omitted, a
provider-bearing local claim selects the existing lease's provider before that
provider is configured. Exact lease IDs take precedence. Slug matches may span
multiple scopes of one canonical provider, which that provider resolves; claims
from different providers require a canonical ID or explicit provider. An
explicit --provider remains authoritative.
For coordinator-backed preparation of an exact lease ID, an initial lease-read HTTP 5xx response is retried once within the original 30-second control budget; shorter HTTP-client and caller deadlines still win. Authentication, absence, conflict, identity mismatch, cancellation and timeout failures are not retried. This repeats only the observation before SSH and script admission; it never reruns a script. Plain status and Stop retain their existing observation behavior.
If the coordinator has confirmed a lease's provider cleanup, run --id fails
immediately instead of waiting for SSH on the deleted machine. status and
stop remain available to inspect the outcome and finish local cleanup.
For an ordinary reused coordinator lease, --ssh-port <port> pins one of the
lease's advertised primary or fallback SSH ports before workspace ownership or
command delivery. An unadvertised port is rejected; the lease's host, user,
credentials, and host-key policy remain unchanged. Explicit selection disables
automatic port fallback for that connection and does not update the broker's
lease metadata. Without an explicit selection, normal port probing is unchanged.
The existing ssh.port configuration and CRABBOX_SSH_PORT environment input
use the same selection rule, including for desktop commands that do not expose
--ssh-port. Provider release remains independent of guest-port selection.
Previously ignored explicit settings now take effect: remove an obsolete
--ssh-port, ssh.port, or CRABBOX_SSH_PORT override to retain automatic
selection instead of failing on an unadvertised port.
For --pool, the pool-recorded endpoint takes precedence over explicit port
flags, configuration, and environment inputs.
With --pool <key>, Crabbox borrows one hydrated broker ready-pool lease,
uses the pool-recorded SSH endpoint, keeps the borrow deadline alive while it
runs the command and return-time scrub, and then returns the lease.
Before a reusable return, Crabbox resets the checkout to the pool's recorded
branch, fetches its latest remote commit, removes run-local state, and verifies
the normal Git worktree is clean. The default --pool-return auto scrubs and
returns successful runs; command, lifecycle, or scrub failures drain the lease
so a bad or de-hydrated machine is not reused. Ignored task state is removed.
Successful runs may retain explicitly ignored dependency install trees.
Actions-hydrated leases drain if
their recorded hydration commit no longer matches the prepared branch commit.
Submodule worktrees drain instead of being reused. Reuse
requires a canonical HTTPS origin that succeeds
through a credential-free fetch preflight before the lease is borrowed. SSH,
local/file, query/fragment-bearing, and private credential-backed origins are rejected; use a forced
drain or release return policy for those repositories. Use
--pool-return ready|drain|release to override the
lifecycle policy for one run; ready still requires a successful scrub. See
Broker ready pools.
Pooled runs reject --full-resync/--fresh-sync. Reusable pooled runs require
a branch ref. With --no-sync, pooled borrows also require an exact commit
match. Use a forced drain or release policy for exact SHA or tag refs.
Reusable pooled runs also reject --fresh-pr; use a forced drain or release
for one-shot PR work.
Pooled runs also reject --keep and
--keep-on-failure; use --pool-return ready|drain|release for lifecycle.
Use --pool-identity-file to explicitly opt into a provider-scoped,
image-pinned typed pool. Create the file with crabbox pool identity <key> --id <lease-id> --cache-compatibility <value>. The repository seed, provider-owned
immutable source, canonical architecture, and operator-declared cache value
must match exactly. AWS binds the AMI and region; GCP binds the numeric image or
disk-snapshot ID and source project/collection while allowing the launch zone
to vary. Unexpected identity or lease evidence drains the entry. Older
coordinators fail explicitly instead of borrowing from a legacy pool. Existing
--pool calls without this flag retain their provider-neutral legacy behavior.
On coordinator-backed one-shot runs, if SSH becomes unavailable after a
successful sync but before the command starts, Crabbox stops that stale lease,
creates one replacement lease, and retries sync once. It does not replace
explicit --id, kept, --keep-on-failure, --no-sync, --sync-only, or
custom-slug runs.
If the local SSH multiplexing socket is temporarily full before a command starts, Crabbox retries that same session once, then disables multiplexing for that invocation if the exact file-descriptor handoff failure recurs. The existing lease and command are preserved; ordinary remote command failures do not trigger this multiplexing recovery.
Crabbox records a local repo claim for each reused lease. If a lease is already
claimed by another repo, pass --reclaim to move the claim intentionally.
For already-bound canonical IDs on native AWS, Machine0, and Daytona, run
admission holds that claim through provider preparation and endpoint publication.
A concurrent heartbeat cannot invalidate the command between those steps.
Aliases, explicit reclaim, and coordinator-managed leases retain their existing
resolution paths; stale heartbeat snapshots still fail the exact-claim check.
--idle-timeout controls inactivity expiry (default 30m); --ttl is the
maximum wall-clock lifetime (default 90m). Use --stop-after success|always|failure|never to make lease cleanup explicit. Without it, a
newly acquired one-shot lease is released after the command and an existing
--id lease is left alone. The run details always print the exact crabbox stop ... command. Use --keep-on-failure to keep a newly acquired lease alive
for debugging when the remote command exits non-zero; Crabbox then prints
inspect/SSH/stop commands for the exact failed box. Add --lease-output <file>
with --keep to write a small JSON lease handle for orchestrators on providers
that advertise run-session. Delegated providers return their own handle.
AWS and local-container opt into the same schema through the core SSH path: a
fresh run requires --keep, while a reused --id run requires the default or
--stop-after never policy so the lease cannot be released after the handle is
reported. Conflicting policies are rejected before acquisition. Core writes the
handle after recording the exact lease claim and before sync or command
execution, so later run failures leave the cleanup handle available. The handle
contains only the provider, exact lease ID, optional slug, reused/kept state,
optional run ID, and the exact crabbox stop cleanup command. On brokered AWS,
the run ID is the coordinator run that can later resolve to a signed terminal
receipt; direct AWS run IDs are local correlation identifiers only.
Delegated providers
Most providers connect over SSH and Crabbox owns sync and command transport.
Delegated providers (for example Blacksmith Testbox, Blaxel, Daytona, Islo,
Azure Dynamic Sessions, Cloudflare Dynamic Workers, Cloudflare Sandbox, E2B,
Nomad, Superserve, OpenSandbox, and Vercel Sandbox) own command transport
themselves: Crabbox sends either checkout content or module source through the
provider's APIs, runs through the provider, and prints sync=delegated in the
final timing summary where a sync phase exists. These
providers reject the SSH-run-only features --capture-stdout,
--capture-stderr, --capture-on-fail, --script, --script-stdin, and
--fresh-pr unless a delegated adapter advertises the matching capability.
Module-runtime delegated providers use --script <file> or --script-stdin as
source module input and reject trailing -- <command> argv because they do not
provide a Linux shell. Delegated artifact features such as --artifact-glob,
--require-artifact, and --download are accepted only by delegated adapters
that explicitly advertise the matching bounded artifact capability.
--keep-on-failure is supported for one-shot delegated runs. See the
per-provider docs under providers for how --id
resolves and any extra sync limitations.
Vercel Sandbox forwards non-auth command environment values through the SDK
bridge request body and strips Vercel provider auth variables from
--allow-env forwarding. Use Crabbox env forwarding for live secrets; raw
sandbox --env key=value places values on argv and is only suitable for manual
non-secret debugging.
Cloudflare Sandbox forwards non-auth command environment values through the
bridge request body and strips Cloudflare provider auth variables from
--allow-env forwarding. Its client accepts either server-sent exec output or a
buffered JSON exec result, and archive sync is proofed through fake bridge tests
rather than live Cloudflare writes in the default test suite.
--azure-backend dynamic-sessions keeps --provider azure as the family
selector while routing to the azure-dynamic-sessions delegated backend.
--provider blaxel creates or reuses a Crabbox-claimed Blaxel Linux sandbox,
uploads the checkout as an archive through the Blaxel file API, executes the
command through the Blaxel process API, and mirrors the remote exit code.
--sync-only, --no-sync, --force-sync-large, --keep, and
--keep-on-failure follow the delegated-run contract; SSH-only run features are
rejected.
--provider cloudflare-dynamic-workers is a module-runtime provider. It accepts
Worker module source through --script or --script-stdin, supports cache and
egress controls through --cloudflare-dynamic-workers-* flags, and rejects
Linux shell semantics such as trailing command argv, SSH, sync-only, ports,
Actions hydration, browser, desktop, code-server, --class, and --type.
--provider docker-sandbox --docker-sandbox-clone has one provider-local
exception to the default one-shot cleanup rule: if Crabbox creates a fresh
clone-mode sandbox for the run and the command succeeds, Crabbox keeps that
sandbox even without --keep. This preserves unfetched in-sandbox commits.
Crabbox prints the exact crabbox stop --provider docker-sandbox <slug>
command for manual cleanup. Reused --id Docker Sandbox runs keep their
existing lifecycle behavior.
--provider coder remains on the normal SSH-run path. Crabbox asks the local
coder CLI to create or start a Linux workspace, syncs into
coder.workRoot, runs the command over coder ssh --stdio, and then stops the
workspace by default for one-shot cleanup. Set --coder-delete-on-release only
for disposable workspaces that should be deleted instead of stopped.
Sync
Sync builds a file manifest with git ls-files --cached --others --exclude-standard, then feeds that manifest to rsync over SSH. Tracked files
plus non-ignored untracked files transfer; .git, ignored build output,
dependency folders, .crabboxignore patterns, sync.exclude patterns, and
common caches stay out. Default excludes also cover common generated churn such
as .ignored, .vite, playwright-report, test-results, and local
.crabbox log/capture directories.
Jujutsu workspaces are supported for sync only when .jj is colocated with
same-root .git metadata. Native Jujutsu revision mapping is not supported yet;
run fails before lease acquisition or ready-pool borrowing instead of risking
sync of an outer Git checkout's revision. Use a colocated Git workspace or pass
--no-sync with a provider that supports it to run without transferring local
files. See
sync for safe initialization guidance.
Before the first rsync into a Git checkout, Crabbox seeds the remote worktree
from your origin remote so the first sync is a dirty-tree overlay instead of a
full source upload. Crabbox also records a local/remote sync fingerprint and
skips rsync when the tracked commit, manifest, and dirty metadata have not
changed.
Existing SSH leases use one remote, lease-scoped workspace owner before Crabbox
reads hydration state, Git metadata, or the sync fingerprint. The owner remains
held through sync or fresh checkout, Actions hydration, the command, result and
artifact collection, failure capture, and ready-pool scrub/return. Separate
clients and watch iterations therefore cannot mutate or execute the same
reused workspace concurrently. A contending client waits for a bounded interval
and prints periodic progress. Newly acquired exclusive one-shot leases bypass
this owner on POSIX and WSL2 targets. Native Windows also uses the owner for
fresh one-shot runs: its witness stages inherited SSH input into an ordinary
redirected file stream before upload, sync, or user commands read it.
Ownership is fenced with a random token and renewed while the lifecycle is active. If the local client disappears, Crabbox recovers an expired owner only after verifying that its witnessed remote child is no longer alive. Ambiguous renewal, release, token, or child state fails closed instead of risking a concurrent checkout. POSIX, WSL2, and native Windows targets implement the same protocol; the small sync-finalization lock remains nested inside it.
Renewal errors retain recognized MISMATCH, EXPIRED, and AMBIGUOUS protocol
states alongside transport errors. Unrecognized response text is omitted.
WSL2 renewal uses a compact marker-only helper with a 60-second execution
allowance for CPU and disk contention. It retries confirmed lock contention at
most twice within the original bounded call deadline; that deadline is included
in the owner expiry window. A transport failure or rejected/ambiguous owner
state is never retried. Collection and cleanup remain blocked after ownership
fails closed. Linux and native Windows renewal behavior is unchanged.
Native Windows stages owner scripts and witnessed command input with exact byte counts and asynchronous pipe reads. Empty frames complete without initializing stdin; nonempty frames leave any following bytes available. Incomplete input fails before the staged script or command runs. A transport failure during renewal still fails closed.
Use --full-resync (alias --fresh-sync) when a warm lease smells stale:
Crabbox deletes the remote workdir, skips the fingerprint fast path, reseeds Git
when possible, and uploads the checkout from scratch. Use --checksum for a
paranoid checksum scan instead of size/time comparison, and --debug to print
sync timing, progress, and itemized rsync output.
After sync, Crabbox runs a remote sanity check. If the remote checkout reports
at least 200 tracked deletions, the run fails before the command unless local
CRABBOX_ALLOW_MASS_DELETIONS=1 is set.
Project-specific excludes live in .crabboxignore or sync.exclude in
crabbox.yaml / .crabbox.yaml. See sync. Use
crabbox sync-plan to inspect the same manifest without leasing
a box.
Large sync guardrails
Before rsync starts, Crabbox prints the candidate file count and byte estimate.
Large syncs warn or fail according to sync.warnFiles, sync.warnBytes,
sync.failFiles, and sync.failBytes; use --force-sync-large or
sync.allowLarge: true only when the size is intentional. Large-sync warnings
list the top source directories by file count plus a hint to update
.crabboxignore or sync.exclude. Quiet rsync runs print a heartbeat; after
several minutes without visible progress the heartbeat includes a concrete retry
hint, and sync.timeout kills stalled syncs.
Sync alternatives
--no-syncskips local file transfer and--sync-onlysyncs and exits on supported providers. Blacksmith Testbox rejects both: its native command owns sync even with--id, so reusing a Testbox does not provide a sync bypass. Provider admission runs before backend configuration; skipping sync does not skip provider initialization or existing-lease preparation.--fresh-pr <owner/repo#number|url|number>skips local dirty sync and creates a fresh remote checkout of a GitHub PR. A bare<number>uses the current repository's GitHub origin. Onlygithub.comPR URLs are accepted; other hosts are rejected. Add--apply-local-patchto apply the localgit diff --binary HEADon top of the PR checkout.--fresh-prneeds the SSH-run sync path; delegated providers reject it. Native Windows SSH targets are supported.
Actions hydration
When the lease was hydrated by crabbox actions hydrate, run
reads the remote marker under $HOME/.crabbox/actions, syncs into the
workflow's $GITHUB_WORKSPACE, and sources the non-secret env file written by
the workflow. If no marker exists and actions.workflow is configured, run
performs local Actions hydration automatically after sync unless --no-hydrate
or --no-sync is set. This preserves the setup the workflow performed:
checkout path, installed dependencies, caches, runner temp/toolcache paths, and
any project-specific preparation. See
Actions hydration.
Standalone crabbox actions hydrate --id ... acquires the same remote workspace
owner as ordinary reused runs, so hydration cannot overlap a sync, command,
collection, or cleanup from another client.
For an adopted Actions workspace, --full-resync invalidates the readiness
marker before resetting the remote tree. A command-bearing run continues only
when automatic local hydration can rebuild the canonical lease workspace; it
otherwise fails before reset. Omit --no-hydrate and use the canonical
workspace for the command path, or use --sync-only to reset and sync without
rehydration or a user command. The full invalidation, reset, sync, hydration,
and command sequence remains under the reusable workspace owner.
If a JavaScript package-manager command (pnpm, npm, node, corepack)
runs on a raw SSH workspace before a hydration marker exists and no automatic
hydration is available, Crabbox probes the remote tool first and fails before
sync with guidance to hydrate, include runtime setup in the command, or choose a
provider/image with the JavaScript toolchain.
Capabilities
--browser provisions or requires a known browser binary and injects
CRABBOX_BROWSER=1, BROWSER, and CHROME_BIN into the remote command. It
does not imply --desktop; use it alone for headless browser automation.
Browser login/profile state is not managed by Crabbox.
--desktop provisions or requires a visible desktop/VNC session and injects
CRABBOX_DESKTOP=1. Linux defaults to XFCE on DISPLAY=:99; leases created
with --desktop-env wayland expose XDG_RUNTIME_DIR and WAYLAND_DISPLAY
from /var/lib/crabbox/desktop.env instead. Use --desktop --browser for
headed browser automation in the VNC-visible session.
--code provisions or requires a Linux lease with code-server. Use crabbox code --id <lease> to expose the editor through the authenticated
portal.
Reusing a lease requires matching capability labels. See capabilities.
Network
--tailscale asks new managed Linux leases to join the configured tailnet.
--network selects how Crabbox resolves SSH for reused leases and for the final
connection after a new lease becomes ready: auto prefers Tailscale when
metadata exists and SSH is reachable, tailscale fails if the tailnet path is
not available, and public forces the provider host. See
Tailscale.
Targets
For --provider ssh with --target macos and --target windows --windows-mode wsl2, sync uses the same POSIX rsync flow. Native Windows mode
(--windows-mode normal) uses PowerShell over OpenSSH and sends the manifest as
a tar archive into static.workRoot; cache purge and GitHub Actions runner
registration remain Linux-only.
On native Windows, plain argv is best for a single executable such as dotnet test. Use --shell for multi-statement PowerShell snippets, env inspection, or
PowerShell expression syntax, and --script <file.ps1> for longer runs. Crabbox
writes uploaded Windows scripts as UTF-8 with a BOM when the input has none, so
Windows PowerShell 5.1 does not treat non-ASCII source as the system ANSI code
page.
Native Windows background processes
Managed native Windows leases install Node 24.19.0 and npm when either runtime
is missing or broken. The checksum-pinned x64 or ARM64 runtime lives in
C:\Program Files\nodejs on the machine PATH; working existing installations
are retained. Readiness requires both version commands to succeed.
To launch a native Windows daemon that survives the command and SSH session, use the managed lease's explicit detached launcher from a PowerShell script:
$daemonPid = Start-CrabboxDetachedProcess.ps1 -FilePath powershell.exe `
-ArgumentList '-NoProfile -Command "Start-Sleep 600"' `
-WorkingDirectory $PWD.Path
Write-Output "daemon_pid=$daemonPid"
Check it from a later run with Get-Process -Id <daemon_pid>. For a real
service, pass its executable and a single Windows command-line argument string;
quote paths containing spaces inside that string. The launcher inherits the
calling user's identity and environment, returns the child PID, and gives it a
private hidden console without inheriting SSH input/output/error handles. Have the service write its own log
files. It lives until it exits, you stop it, or the managed lease is destroyed;
keep daemon files outside a workspace you intend to replace with --full-resync.
Start-Process -WindowStyle Hidden alone does not escape OpenSSH's Windows
session job, which kills its descendants when the session closes. The launcher
uses Windows' explicit job-breakaway flag, permitted by managed OpenSSH, without
changing session policy. A host that denies breakaway returns an error. Ordinary
commands, command timeouts, workspace-owner renewal, and result collection keep
their existing supervision. The launcher is installed at
C:\Program Files\Crabbox\bin\Start-CrabboxDetachedProcess.ps1; stock leases
created before this bootstrap change need to be recreated.
Scripts
Use --script <file> or --script-stdin for multi-line remote commands. On
POSIX SSH leases, Crabbox uploads a standalone, content-hashed copy into
.crabbox/scripts/ under the remote workdir and executes that copy with the
workdir as its process PWD. $0 identifies the generated upload path, so
dirname "\$0" resolves to .crabbox/scripts/, not the script's original local
directory. That directory component is not preserved in the uploaded copy and
cannot be recovered from $0. Standalone uploaded scripts should resolve
synced project assets from $PWD.
If a Git-managed script needs its synced repository path or adjacent assets, invoke it as trailing argv so the project copy runs in place:
crabbox run -- ./scripts/check.sh
On POSIX SSH targets, automatic failure bundles include only the current run's
uploaded script file, when still available—not the retained .crabbox/scripts
directory or neighboring files. Runs without an uploaded script select none
of that store. A shebang is honored on POSIX targets; scripts without one run
through bash. Native Windows
targets run uploaded scripts through Windows PowerShell, and
--script-stdin is treated as a PowerShell script; a non-.ps1 script path
gets a .ps1 extension added before upload. Trailing arguments after -- are
passed to the script. This is an SSH-run feature for OS-backed providers.
Delegated module-runtime providers that advertise module-run accept the same
script flags as source module input, but they reject trailing command argv and
--shell; they do not imply shell, SSH, rsync, or POSIX filesystem behavior.
Use --script <file> when the runtime needs a filename extension to identify
the module language. --script-stdin is JavaScript module source.
For Cloudflare Dynamic Workers, the script body must be Worker module source, for example:
export default { fetch() { return new Response("ok") } };
Live secrets and env forwarding
Use --env-from-profile <file> with --allow-env <name> for live secrets.
Crabbox parses simple profile lines without executing the profile, forwards only
allowed names, and prints redacted presence/length metadata instead of values.
--allow-env and --env-from-profile are repeatable, and --allow-env also
accepts comma-separated names. Native Windows profile files are uploaded as
UTF-8 and imported with PowerShell UTF-8 decoding so non-ASCII values survive.
POSIX SSH targets also probe the uploaded profile from inside the remote workdir
and print redacted remote presence metadata before the command runs.
Add --env-helper <name> to persist a reusable helper at .crabbox/env/<name>
for that lease; the helper sources the matching profile and execs the command
you pass it. Persist helpers only on boxes you control, because the profile
stays on the remote workdir until you delete it or reset the lease. See
env forwarding.
Preflight
--preflight prints a target-specific capability snapshot after sync and before
the remote command. It is diagnostic only: Crabbox does not install tools,
change the machine, or fail just because a tool is missing. Install logic
belongs in Actions hydration, a prebaked image, a devcontainer, Nix/mise/asdf,
or the command/script you run.
By default it probes common language and infrastructure tools plus OS-specific
basics. Run crabbox preflight-tools, or add --json, to
inspect every accepted name, aliases, default membership, and target support
from the installed binary. Discovery works offline without configuration or a
provider and does not run probes.
Use --preflight-tools to replace the default tool list for one run:
crabbox run --preflight --preflight-tools node,bun,docker -- bun test
crabbox run --preflight --preflight-tools default,uv -- node --test
crabbox run --preflight --preflight-tools default,cmake -- cmake --build build
crabbox run --preflight --preflight-tools python,python3 -- python3 -m pytest
crabbox run --preflight --preflight-tools raw_socket -- ./packet-tests
crabbox run --preflight --preflight-tools none -- ./smoke.sh
default (alias defaults) expands to the default probe list; none alone keeps
only the workspace summary, while mixed none,git still selects git. Unknown
tool names fail before leasing and point to crabbox preflight-tools so typos do
not hide missing diagnostics. Unsupported OS-specific probes are skipped for the current target.
The CMake probe invokes the literal cmake --version command on POSIX, WSL2,
and native Windows targets. It prints only the first output line when CMake is
present or cmake=missing when it is unavailable; either result is diagnostic
only and does not block the workload. There is no cmake3 alias. The Python
probes likewise invoke the literal requested command with --version, including
python and python3 on native Windows; Crabbox does not map either name to
py. An unavailable literal command prints <name>=missing and the run
continues.
raw_socket uses python3, then python, to open and immediately close
socket(AF_INET, SOCK_RAW, IPPROTO_RAW) without binding, connecting, sending,
or receiving. Its stable result is direct, sudo, unavailable, or
probe_missing. sudo means only that the same bounded probe succeeded through
non-interactive sudo -n; Crabbox never elevates the workload, grants
capabilities, installs software, or changes an image or container. The probe is
separate from Python, Scapy, tcpdump, libpcap, and packet-capture availability.
Unsupported targets skip it through normal preflight target filtering.
Configure the default per repo:
run:
preflightTools:
- raw_socket
Profiles, presets, and proof
Configured profiles can define reusable presets. --preset <name> expands the
profile command before execution, applies profile and preset environment
defaults, and prints the expanded command for auditability. --scenario <value>
sets the common {{scenario}} variable; use repeatable --preset-var name=value for other placeholders. Profile doctor checks run before the remote
command when the selected profile enables them, so missing Node, pnpm, Docker,
Compose, or disk prerequisites fail before the expensive lane starts.
Use --emit-proof <path> to render a Markdown ## Real behavior proof block
after a successful run, derived from run metadata, the expanded command,
selected live console output, collected artifact paths, and the
--proof-template or preset template. Keep proof templates in repo config so
parser-sensitive PR wording stays project-owned. Default headings are
context-neutral; put patch- or fix-specific claims in repository-owned template
fields only when the run actually proves them.
Use --attest <path> to write a signed receipt after a command completes,
including non-zero exits. SSH terminal receipts use schema v2 and bind the final
run outcome, raw command digest, timing, retained-log digest, and full observed
stream digest. Delegated providers retain schema v1 when they report a
definitive command exit. Check local receipts with crabbox verify.
Secondary cleanup errors do not suppress a receipt for an already-observed
delegated command exit; provider/transport failures without a definitive exit
do not produce that receipt.
Brokered runs submit a schema v2 terminal receipt with the finish request even
when --attest is omitted. The CLI verifies that the coordinator returns the
exact persisted receipt before treating the finish as recorded, so a
coordinator that predates receipt storage fails closed instead of silently
discarding the evidence. Deploy the coordinator before distributing a
receipt-bearing CLI. Retrieve and verify committed evidence later with
crabbox receipt <run-id>. Signing uses a per-user Ed25519 key
minted on first use under the user config dir. Pass --attest-key <path> to
use an existing PKCS8 PEM Ed25519 key instead.
The coordinator creates the run record before remote command execution and the
CLI prints its ID. An orchestrator that must recover the ID after losing the
client output should also use --lease-output <file> on a supported retained
run. That file is written before SSH wait, sync, or command execution and
includes runID; its existing retention and stop-policy requirements still
apply.
Artifacts and downloads
Use repeatable --artifact-glob <glob> to collect matching remote files after a
successful SSH-backed run. Globs resolve relative to the remote workdir and are
stored locally under .crabbox/runs/<run-or-lease>/ as a tarball. Profile and
preset artifactGlobs are collected the same way. Delegated providers accept
artifact globs only when their adapter advertises bounded run artifact
retrieval; otherwise they reject the flag. Ordinary SSH-backed Linux, macOS,
and Windows WSL2 targets support artifact globs; native Windows still rejects
them. A macOS target needs stock /bin/bash, find with -print0, tar,
base64, and /bin/rm. Crabbox does not traverse directory symlinks while
matching. A matched leaf symlink is accepted only when it points to a regular
file.
Use repeatable --require-artifact <glob> when a successful command must emit a
proof file, manifest, report, or other evidence artifact. Required artifact globs
are checked after the remote command exits 0 and before --download files are
written locally. They are also collected into the run artifact tarball. If any
required glob matches nothing, the run fails even though the command itself
succeeded. On SSH-backed runs, required-glob, required-change, and artifact-schema
validation failures
retain exit 7 and report blockedStage=artifacts with errorKind=provider-error,
so they are distinct from a workload that exits 7 (command-exit). The failure
digest identifies the artifacts phase and area. Cancellation or deadline
observed when validation fails retains its normalized outcome; positive memory
exhaustion evidence keeps priority. Artifact classification alone does not
change retry eligibility. Matches must resolve to regular files, so dangling
symlinks and
symlinks to directories do not satisfy the proof gate. The same SSH-run target
limits as --artifact-glob apply. Delegated providers that support bounded run
artifact retrieval enforce provider-owned file and byte limits before returning
local artifacts.
Blacksmith Testbox collects requested artifact globs in the same native run
after a normal terminal exit, including nonzero exits below 128. It does not
retry, re-sync, or recover files from stopped leases. A fresh complete invocation
receipt and clean native CLI completion are both required before local
publication under the original claim fence; cancellation, sync timeout, or
transport failure withholds artifacts. Signal-like exits skip collection.
Required globs remain all-or-nothing. Collection/cleanup errors preserve an
earlier nonzero workload exit; collection failure after workload success still
fails the run. Limits remain 256 files and 10 MiB compressed, with existing
protected-path and symlink checks. Remote Linux timeout with --kill-after
is required for a separate 30-second collection budget; caller cancellation
wins and the local post-exit wait is also bounded. Collection uses the initial
remote cwd, or a CI-prepared artifact workspace captured before the child starts;
the child's directory changes cannot redirect it. Command timing ends at the
workload receipt, while collection and cleanup count toward total. Evidence
retrieved after failure is not success proof or attestation of exact remote Git
bytes; --emit-proof stays success-only. See the
Blacksmith contract.
Use repeatable --require-artifact-change <path> for created-or-changed byte
evidence on ordinary Linux SSH runs. Every exact relative path must be a regular
file with no symlink components. Crabbox compares bounded content snapshots
after sync/hydration and after successful execution; unchanged bytes (including
identical rewrites) or missing files fail with exit 7, before schema validation
or downloads. Only accepted paths enter the archive, using the checked snapshot
bytes even when broader artifact globs are supplied. Existing flags retain their
behavior without this opt-in mode. Workload/transport failures preserve their
result and record not-evaluated in timing JSON's artifactChanges list.
Limits: 32 paths, 1 KiB per path, 5 MiB per file, 20 MiB per snapshot. Delegated
providers, macOS, WSL2, native Windows, and --sync-only are unsupported. See
Artifacts for the exact states
and collection contract.
Use repeatable --download remote=local when the command writes proof files on
the box. Downloads run only after a successful remote command, paths resolve
relative to the remote workdir unless absolute, and Windows paths use =
instead of : so drive letters stay unambiguous. Crabbox rejects local output
path collisions between lease output, stdout capture, stderr capture, and downloads
before acquisition, including canonical aliases and existing hardlinks. On
Unix-like hosts, Crabbox-created download, capture, proof,
and failure-bundle files use owner-only permissions (0600), and newly created
output directories use 0700.
SSH downloads stream into a private temporary file and publish atomically only after the remote command and advertised byte count pass. Ordinary downloads intentionally enforce a 1 GiB per-file limit and retain at least 1 GiB of local free space as an upgrade safety boundary. A failed, canceled, oversized, or size-mismatched transfer leaves an existing destination unchanged. Automatic remote failure-capture payloads use a tighter 64 MiB limit; their scratch manifests and file lists live outside the tested checkout and are removed on every exit. Failed or canceled capture preparation also removes its partial remote archive with bounded cleanup that does not inherit caller cancellation.
Use repeatable --download-on-failure remote=local to retrieve explicitly
selected evidence after a nonzero workload exit on ordinary Linux SSH runs.
Crabbox requires a fresh owned workload-start/exit marker pair and successful
SSH completion; a workload exit of 255 is distinguishable from SSH transport
loss. The observed workload is never retried on a fallback SSH port. Setup,
sync, hydration, acquisition, preflight, transport, cancellation, and timeout
failures do not authorize these downloads. A JUnit policy failure after a zero
workload exit does not authorize them either, nor does a failed
--require-artifact-change guard. When both flags are used, a confirmed nonzero
workload exit can download evidence while the freshness guard remains
not-evaluated; the download does not claim that the file changed.
Retrieval precedes the automatic failure bundle and lease teardown, including
--stop-after always. A missing/unreadable file or local write failure produces
a warning and does not prevent the remaining selected downloads. Each retrieval
has a 30-second limit; the original workload exit and failure classification
remain authoritative. The existing single-file transport, remote path behavior,
atomic local writer, and private modes are reused. Destinations are checked for
collisions across success/failure downloads, captures, proof, receipt, and lease
output before acquisition, leaving existing bytes unchanged on rejection.
Existing --download stays success-only. macOS, WSL2,
native Windows, delegated execution, and --sync-only reject the first-slice
flag. Exit markers are removed from captured/logged stderr and leave no remote
marker files. This does not establish artifact freshness.
See artifacts for the richer collection and publishing workflow.
Output capture
Use --capture-stdout <path> when stdout is binary or terminal-hostile. Crabbox
writes the remote stdout bytes directly to the local file, leaves stderr on the
terminal, and skips stdout run-log/event capture. --capture-stderr <path>
works the same way for stderr. Both are SSH-run-only; delegated providers reject
them. When --emit-proof is also set, the proof includes the safely escaped
local capture path and final byte count for each redirected stream, but never
reads or embeds the captured bytes. Any other live console output remains in
the proof's redacted tail excerpt.
When the remote command exits non-zero, Crabbox writes a local-only
.crabbox/captures/*.tar.gz failure bundle by default. POSIX SSH-backed bundles
include the current run's uploaded script file (when available), redacted
env/config summaries, timing JSON, command stdout/stderr, common debug paths such as test-results,
playwright-report, coverage, JUnit XML files, nearby *.log files, and a
gateway log tail when a known gateway log path exists. The exact .crabbox/scripts
store is excluded from general report/log discovery, including prior uploads
and report-looking neighbors. Explicit artifact/download selections remain
independent; use --download-on-failure to request additional files after an
eligible failure. Native Windows bundles remain local-only. Implicit stdout/stderr
entries are capped to keep bundles bounded; explicit --capture-stdout /
--capture-stderr files are included as caller-created local files.
Remote archive entries are confined to the bundle subtree; unsafe links and
special files are omitted.
--capture-on-fail remains accepted as a compatibility alias. Crabbox does not
redact captured files; the caller owns redaction before sharing them.
When the project capture destination is unwritable, automatic bundles fall
back to the Crabbox user state directory's captures/ subdirectory. The
failure-bundle local=... line reports the actual path. Security-validation
and archive/read failures do not trigger fallback, and explicit stdout/stderr
capture paths never move. See local capture storage
for the state path and retention details.
Test results
Add --junit <path> (comma-separated) or configure results.junit to attach
JUnit XML summaries to the run record. Use --results-auto or results.auto: true to scan common remote JUnit XML paths written by the command; auto
discovery skips dependency and Git directories and bounds remote file reads
before parsing. Malformed or over-limit reports produce named warnings while
valid reports remain attached. Add --fail-on-test-failures (or configure
results.failOnFailures: true) to exit 1 when a successful command writes
JUnit failures or errors. crabbox results <run-id> then prints failed
tests without reading the raw log. See test results.
Output and observability
Before sync, run prints a compact context block with run ID, portal/log URLs,
lease ID, slug, provider, SSH target, remote workdir, and whether the workspace
is raw or Actions-hydrated.
For newly created brokered leases, run also prints the exact selected image
ID/source and provider-side request, network-readiness, bootstrap, and total
startup timings when the provider reports them.
At the end of every command, run prints a one-line timing summary (lease,
bootstrap, sync, command, total, and end-to-end duration; whether sync was
skipped by fingerprint; and the remote exit code), followed by run details with
provider, lease ID, slug, run ID, machine type, repo path, remote workdir,
Actions URL when present, stop command, and idle timeout. Add --label <text>
to attach a short label to the run details, timing JSON, and coordinator run
record.
When a remote command exits non-zero, run prints a compact failure digest
after automatic cleanup. Lease recovery commands are omitted after confirmed
terminal release, including deletion that leaves a fixed-ID receipt. They remain
available for retained resources and pending or unconfirmed cleanup. Preserved
recovery state does not imply that the resource is running or reachable; inspect
its status before reuse, or retry the printed stop command to finish cleanup.
The digest includes the failed phase when phase markers are known, a
likely area (provider auth, SSH/connectivity, sync, install/setup, user command,
model/tool/provider limit, or resource exhaustion), retryability when inferable, next commands
(logs, events, doctor --from-run, ssh, retrying, and stop). Unknown
failures stop at the run-scoped diagnostic commands instead of advertising a
full rerun. A retry is printed only when the failure is classified as retryable
and preserves an explicitly requested --no-sync, so it does not reset the
retained workspace. Other retries retain the --fresh-sync guidance above. Each original
--require-artifact glob is retained in the retry, so missing required evidence
still fails the rerun. After failure-bundle information and command hints, each
stream has one
redacted tail section of up to 40 lines, or its capture path when explicitly
captured. Live output and failure-bundle contents are unchanged. The digest does
not reconstruct secrets or hidden local shell state. Short-circuit explanations are limited to simple
&& chains; compound commands and substitutions are left unattributed.
When an SSH backend supplies per-run memory
exhaustion evidence, the summary and digest use
blocked_stage=resource_exhaustion resource_exhaustion=memory retry_likely=false
and prefer the provider's bounded contextual hint. Without usable context,
advice is to reduce memory demand and inspect active limits and runtime capacity
before retrying. Exit 137 alone is not positive OOM evidence. Evidence read
failures are warnings and do not replace the original command failure.
Use --timing-json to emit a final JSON timing record with provider, lease ID,
slug, run ID, machine type, repo path, remote workdir, lease acquisition,
bootstrap, sync phases, command phases, command duration, command-path total,
end-to-end duration, exit code, normalized runStatus, optional errorKind,
stop command, artifacts, and Actions run URL when available. runnerTotalMs
measures local wall time through route cleanup. runnerPhases provides a
bounded, timing-only breakdown; accepted phases never exceed the total, and
unclassified remainder is reported as unattributed or, for delegated
providers, an opaque delegated phase. Provider-supplied coordinator phases are
limited to request, network_ready, bootstrap, and unattributed.
Malformed vectors are discarded and valid legacy startup scalars remain the
fallback. Failed runs also
include blockedStage, resourceExhaustion, and retryLikely when classifiable.
Optional failureEvidence contains the provider's classification, sanitized
hint, and bounded string-valued details. Invalid optional presentation fields
do not erase valid OOM classification. The same snapshot is copied into local
failure bundles and the deferred digest, so one-shot deletion does not lose it.
For Local Container,
actual container settings, total runtime RAM, and swap are separate observations,
not an exact effective or free-memory bound.
Runner timing is unsigned local telemetry. It is not part of receipt v2, does
not change signing, and must not be treated as attested evidence. App
finalization emits the failure digest, timing record, timing JSON, local receipt
persistence, and coordinator finish in that order after cleanup. Timing sink
failures are terminal and are reflected in the local receipt and process exit;
the executable can subsequently append its existing exit diagnostic. Timing
artifacts contains only files already committed when that timing payload is
emitted. The terminal receipt is persisted afterward, so its metadata is
intentionally excluded; successful persistence prints a separate
artifact kind=receipt path=... bytes=... confirmation.
After an automatic cleanup attempt, leaseStopped reports whether the release
owner confirmed that lease-based recovery is no longer available. An accepted
release alone does not set it to true. leaseStopError independently records a
cleanup error: it can accompany leaseStopped=true when the remote resource is
gone but local finalization failed. An existing workload failure keeps its exit
code. Accepted pending or retained cleanup does not itself turn a successful
workload into a command failure.
Commands can emit
phase markers on stdout or stderr as
CRABBOX_PHASE:<name>; Crabbox records those as commandPhases without removing
the marker line from output. In blacksmith-testbox mode, sync is reported as
delegated in the same schema.
For a failed command, the last observed phase names install, hydrate, or
setup classify as install; build and test classify as themselves (case
insensitive). Emit a marker before each stage. These phases take precedence over
workload error text from earlier stages. Other custom phase names remain visible
in timings but classify as unknown. Without phase evidence, diagnostic text may
identify a failure, but echoed command flags and arbitrary stage receipts do not.
Provider, SSH, auth, and normalized resource-exhaustion evidence retain priority;
structured test-result failure policy is unchanged. A later artifact collection
failure does not inherit a successful workload's phase. Classification does not
alter the command's exit code or output.
Use --timing-record=default or --timing-record <path> to append the final
timing payload to a local benchmark JSONL store. This is opt-in; ordinary
crabbox run invocations do not persist timing rows. The persisted row wraps the
same TimingReport payload with local benchmark context such as command
fingerprint, repo fingerprint, provider family/kind, and cold/warm state when
known. Timing rows, failure bundles, and receipts can contain sensitive local
correlation artifacts such as repo paths, remote workdirs, labels, artifact
paths, lease IDs, and run IDs. Keep them private and review them before sharing.
See crabbox bench for reporting and privacy guidance.
When a coordinator is configured, Crabbox records each remote command as a run
history item. crabbox history lists those records and crabbox logs <run-id> prints retained remote output (retention is bounded so
a noisy command cannot fill storage). See
history and logs.
Pond
Use --pond <name> to tag a new lease into a named pond. Pond is a reserved
provider label that groups peers, and crabbox list --pond <name>
selects them as a set. With --tailscale on a Tailscale-capable provider the
CLI also advertises a tag:cbx-pond-<owner>-<name> ACL tag, and cloud-init keeps
/etc/hosts.cbx plus a managed /etc/hosts block in sync every 30 seconds so
Tailscale peers in the same pond reach each other as <slug>.cbx. See
pond.
Provider notes
For AWS one-shot leases, --market overrides capacity.market for this run.
Explicit --type keeps exact-type semantics; Crabbox reports why that type
failed rather than falling back to a different size.
XCP-ng one-shot leases use the SSH-run path on Linux only. The provider clones
the configured template with config-drive cloud-init, so --script,
--env-from-profile, --capture-stdout, --download, and the other SSH-run
features work after the VM is ready. Run crabbox doctor --provider xcp-ng --json before live runs, and keep CRABBOX_XCP_NG_PASSWORD in env or private
config rather than argv.
Azure one-shot leases use managed StandardSSD_LRS OS disks by default so they
can become native checkpoint sources. Use --azure-os-disk ephemeral only for
stateless leases that do not need native Azure checkpoint/fork support;
--azure-os-disk ephemeral-preview opts into Azure's public-preview
full-caching ephemeral OS disk mode. --azure-os-disk auto is accepted for
compatibility and resolves to managed.
Flags
Lease-create flags (shared with warmup, actions hydrate, and other
lease-acting commands):
--provider <name> See `crabbox providers` for the full list.
--profile <name>
--class <name>
--arch amd64|arm64 CPU architecture; cloud/Apple select capacity, while local-container asserts selected-daemon native architecture.
--os <selector> Portable Linux OS image, e.g. ubuntu:26.04
--type <provider-type>
--market spot|on-demand
--slug <slug> Only when creating a fresh lease.
--pond <name>
--expose <port> Repeatable; SSH-mesh-reachable TCP port.
--cache-volume [name=]key:path
Require a provider cache volume.
--ttl <duration> Default 90m.
--idle-timeout <duration> Default 30m.
--desktop
--desktop-env xfce|wayland|gnome
--browser
--code
--image-min-os <version>
--image-sdk <name=version> Repeatable.
--image-runtime <name=version> Repeatable.
--image-require-browser
--image-require-webview2
--image-require-desktop
--target linux|macos|windows
--windows-mode normal|wsl2
--static-host <host> provider=ssh
--static-user <user>
--static-port <port>
--static-work-root <path>
--network auto|tailscale|public
--tailscale
--tailscale-tags <comma-separated tags>
--tailscale-hostname-template <template>
--tailscale-auth-key-env <env-var>
--tailscale-exit-node <name-or-100.x>
--tailscale-exit-node-allow-lan-access
Provider-specific flags are registered by each adapter and only apply to that
provider (for example --azure-backend, --azure-os-disk, the
--blacksmith-*, --exe-dev-*, --namespace-*, --semaphore-*,
--sprites-*, --e2b-*, and --azure-dynamic-sessions-* families). See the
per-provider docs under providers.
Run-specific flags:
--id <lease-id-or-slug>
--reclaim
--keep
--keep-on-failure
--stop-after success|always|failure|never
--lease-output <file> Write a retained run-session handle when supported.
--no-sync Skip local file transfer; unsupported by Blacksmith Testbox.
--sync-only
--no-hydrate
--full-resync Alias: --fresh-sync
--checksum
--force-sync-large
--debug
--shell
--script <file>
--script-stdin
--fresh-pr <owner/repo#number|url|number>
--apply-local-patch
--allow-env <name> Repeatable or comma-separated.
--env-from-profile <file> Repeatable.
--env-helper <name>
--preset <name>
--scenario <value>
--preset-var name=value Repeatable or comma-separated.
--emit-proof <path>
--proof-template <name>
--attest <path>
--attest-key <path>
--preflight
--preflight-tools <comma-separated tool names>
--junit <comma-separated remote XML paths>
--results-auto
--fail-on-test-failures
--artifact-glob <glob> Repeatable.
--require-artifact <glob> Repeatable.
--require-artifact-change <path> Repeatable; Linux SSH, created or changed bytes.
--download <remote=local> Repeatable.
--download-on-failure <remote=local> Repeatable; confirmed nonzero Linux SSH exit.
--capture-stdout <local path>
--capture-stderr <local path>
--capture-on-fail Compatibility alias.
--label <text>
--timing-json
--timing-record default|off|path