CLI Reference
Because pixi discovers pixi-<command> on PATH, all verbs work as pixi sandbox <verb> too.
Global
Section titled “Global”pixi-sandbox --helppixi-sandbox --versionpixi sandbox --help # via pixi extensionPipeline diagnostics
Section titled “Pipeline diagnostics”pack, doctor, and publish share two diagnostics controls:
pixi-sandbox pack ... -vpixi-sandbox doctor ... -vv --log-file /var/tmp/pixi-sandbox-doctor.logpixi-sandbox publish ... --log-file ./publish-attempt.log-v writes phase boundaries to stderr; -vv also includes reviewed context such as local paths,
platform, environment count, and helper-tool paths. --log-file <PATH> creates a durable plain
text log and flushes every phase plus the complete causal error chain before exit. It works with
or without -v, so CI can keep normal stdout unchanged while retaining evidence. The destination
must not already exist: pixi-sandbox refuses to overwrite the only copy of an earlier failure.
Diagnostics never dump argv or the environment. A configured Git remote is recorded only as
<configured>, and URL credentials plus common token/password query fields are redacted from
nested process errors. Choose a log path outside the transport output: the log is operator
evidence, not payload.
Create transport directory from pixi envs.
pixi-sandbox pack \ --repo-root . \ --envs dev,docs \ --output-dir .sandbox-out \ --platform linux-64 \ --shard-limit-mib 95 \ --cargo-vendor \ --cargo-vendor-mode loose|tarballs \ --fetch-tools \ --tools-lock /path/to/reviewed-tools.lock.json \ --tools-cache ~/.cache/pixi-sandbox/tools \ --self-bin target/release/pixi-sandbox| Flag | Description |
|---|---|
--repo-root |
Project root (default .) |
--envs |
Comma-separated envs |
--output-dir |
Output transport dir |
--platform |
Platform (e.g., linux-64) |
--shard-limit-mib |
Shard limit MiB (default 95) |
--cargo-vendor |
Vendor Cargo deps |
--cargo-vendor-mode |
loose (default) or tarballs |
--fetch-tools |
Fetch pinned tools (sha256-verified) |
--tools-lock |
Override tools.lock.json path |
--tools-cache |
Tools cache dir |
--self-bin |
Standalone binary to embed only under .pixi-sandbox/tools/<platform>/pixi-sandbox (.exe on Windows) — see below |
No override means embedded pins from tools.lock.json compiled into binary.
The self-bin must run standalone. A transport is executed on machines that never saw
your prefix — no pixi global installation, no conda activation, no shell profile — so pack
probes the embedded copy with --version under an empty environment and refuses a binary
that cannot answer. The classic trap is $(command -v pixi-sandbox) after
pixi global install: that is a launcher trampoline, not the binary; it looks for
trampoline_configuration/pixi-sandbox.json beside the executable and dies at restore, and
it passed every other check (faithful copy, static linkage — the self-bin’s content is not
pinned into the manifest). Pack the checksum-verified release asset
(pixi-sandbox-<target> from the release, checked against its SHA256SUMS) or a binary you
built yourself. A pixi global trampoline and a managed launcher script (the
~/.local/bin/pixi-sandbox indirection a restore registers) are refused outright, before
any embedding. Packing for a platform other than the host skips the probe with a printed
note — doctor --verify on the target host is the same guard there.
doctor
Section titled “doctor”Verify transport without writing.
pixi-sandbox doctor --branch-location .sandbox-out --verifypixi-sandbox doctor --branch-location .sandbox-out --verify --envs devpixi-sandbox doctor --branch-location .sandbox-out --verify --jsonWith --verify and an embedded pixi-sandbox tool, doctor also runs the standalone
probe: after — and only after — every declared byte matches the manifest, it executes the
tool’s --version under an empty environment, so a hash-green branch whose bootstrap cannot
run (a packed pixi global trampoline, see --self-bin above) is diagnosed here with the
remedy instead of on the airlock. The probe is skipped, with a printed reason, when the
hash report is not green (unverified bytes are never executed) or when the manifest
platform is not this host’s; a failed probe makes doctor exit non-zero.
Verify a restored project against the manifest’s per-file list, not just the branch:
pixi-sandbox doctor --branch-location .sandbox-out --verify-restored /path/to/project| Flag | Description |
|---|---|
--branch-location |
Transport dir |
--verify |
Verify all blobs |
--verify-restored <PROJECT> |
Also compare the restored tree to the manifest’s per-file digests (implies --verify) |
--work-dir |
Work dir the restore used, if not the default <project>/.pixi/.restore-work |
--envs |
Filter envs |
--json |
Machine-readable output |
Reports all failures, not first. Checks size + sha256 + tool linkage (rejects dynamic).
--verify-restored additionally checks every file of the restored prefix (content, symlink
targets, exec bits, the fingerprint marker) against the files.json oracle the packer
recorded, flags any file present that the transport never carried, and reports schema-1
environments as unverifiable rather than failed. The airlock workflow’s archived ci-feature
e2e gate runs it against the restored project and extracted transport.
publish
Section titled “publish”Publish transport as orphan branch.
pixi-sandbox publish \ --input-dir .sandbox-out \ --branch-name sandbox/dev-linux-64 \ --remote origin \ --keep 5 \ --dry-run| Flag | Description |
|---|---|
--input-dir |
Transport dir |
--branch-name |
Orphan branch to force-push |
--remote |
Git remote (default origin) |
--keep |
Retain at most N snapshots on the branch (default: 1, a single-commit orphan) |
--dry-run |
Preview git commands, don’t push |
Implementation: GIT_DIR points at scratch repo next to transport (same FS, never /tmp), GIT_INDEX_FILE scratch, GIT_WORK_TREE = transport, plumbing add → write-tree → commit-tree (no -p) → update-ref → push --force. No .git in owned dir, no copy.
Rotation (--keep N)
Section titled “Rotation (--keep N)”Without --keep (or with --keep 1) a publish is what it has always been: one parentless
commit that replaces the branch. --keep N keeps the N most recent snapshots instead —
the N-1 previous ones are fetched shallow and --filter=blob:none (commits and trees only, so
rotating does not download the payload it is rotating), re-committed as a fresh chain, and the
new snapshot goes on top. The result is force-pushed like any other publish.
# a branch that carries the last three builds, oldest dropped on every publishpixi-sandbox publish --input-dir .sandbox-out --branch-name sandbox/dev-linux-64 --keep 3restore
Section titled “restore”Airlock restore — verify then materialize. pixi-sandbox init generates project-side
restore and restore.ps1 launchers that archive the selected sandbox ref (fetching that one
branch first when a connected clone lacks it, unless PIXI_SANDBOX_FETCH=skip) and call this
explicit form using its nested binary:
/tmp/sb/.pixi-sandbox/tools/linux-64/pixi-sandbox restore \ --branch-location /tmp/sb \ --output-path /path/to/project \ --envs dev \ --verify-only \ --force \ --no-vendor \ --work-dir /path/to/project/.pixi/.restore-work \ --cargo-config auto|write|print|none \ --user-tools register|skip \ --user-bin /path/to/user/binWhen invoked with no subcommand from inside an extracted branch, the nested binary remains
read-only: it runs doctor --verify and prints the explicit restore command.
| Flag | Description |
|---|---|
--branch-location |
Fetched branch dir (read-only) |
--output-path |
Project output (new flag) |
--path-to-main-repo-code |
Alias for --output-path (backward compat) |
--envs |
Partial restore (vendor always materialized) |
--verify-only |
Verify, write nothing |
--force |
Overwrite existing envs; also replaces unmanaged pixi/pixi-sandbox entries in the user bin directory |
--no-vendor |
Skip vendor |
--work-dir |
Work dir (default .pixi/.restore-work, same FS, TMPDIR redirected) |
--cargo-config |
auto (default) / write / print / none — auto writes sandbox-owned .pixi-sandbox/cargo-home/config.toml plus Pixi activation hooks; write overwrites project .cargo/config.toml; print/none do not wire Cargo |
--user-tools |
register (default) / skip — register pixi + pixi-sandbox for the user after both verifications pass, or touch nothing outside the project (env: PIXI_SANDBOX_USER_TOOLS) |
--user-bin |
Per-user bin directory for the launchers (default ~/.local/bin, Windows %USERPROFILE%\.pixi-sandbox\bin; env: PIXI_SANDBOX_USER_BIN) |
Order: load manifest → verify everything → materialize tools (idempotent) → preflight disk → install envs (pixi-unpack → rename) → write markers (conda-meta/pixi_env_prefix, fingerprint) → vendor + Cargo wiring (auto uses .pixi-sandbox/cargo-home and per-env activation hooks, leaving project .cargo/config.toml untouched) → remove any legacy .pixi/sandbox-env.sh → verify the restored tree against the manifest’s per-file digests → register user tools (launchers + profile/registry PATH, unless --user-tools skip) → assertions pixi install --frozen --offline no-op, and when Cargo is wired, pixi run --frozen -- cargo build --offline uses the vendor tree.
User-tool registration: managed launchers exec the manifest-verified copies under
.pixi/tools/<platform>/; a restore of another project retargets them (the most recent
restore is the user-level source); unrelated existing pixi/pixi-sandbox commands are
refused unless --force. There is no supported .pixi/sandbox-env.sh: pixi is the
sole entrypoint, and package/crate commands should run as pixi run ... (or through the
manifest-owned .pixi/tools/<platform>/pixi when registration is skipped), never as bare
cargo, rustc, or bun commands from a sourced prefix. The only activation hooks restore
writes are per-environment Conda/Pixi hooks that set CARGO_HOME to the sandbox-owned cargo
home when vendored crates are restored in --cargo-config auto mode.
unpack
Section titled “unpack”Single-env primitive, used by restore.
pixi-sandbox unpack \ --input-dir .sandbox-out \ --output-dir /tmp/prefix \ --env dev \ --unpacker /path/to/pixi-unpack \ --force \ --work-dir /tmp/work \ --verify-onlyAccepts transport dir, .pixi-sandbox/envs/<env>/pack dir, or bare pack dir. Leaves pixi markers to restore.
Generate the reviewed project configuration, a reusable GitHub Actions publisher, a lockfile
relock bot, and exactly one launcher for the current platform (restore.sh on Unix,
restore.ps1 on Windows):
pixi-sandbox initpixi-sandbox init \ --github-workflow-path .github/workflows/publish-sandbox.yml \ --relock-workflow-path .github/workflows/relock.yml \ --relock-ci-workflow ci.yml \ --script-path restore.sh \ --config pixi-sandbox.tomlpixi-sandbox init --branch sandbox/developer-linux-64 --forcerelock.yml closes the one gap an airlock cannot close itself: a manifest edit is a text edit
any disconnected host can make, but the solve behind it needs prefix.dev and the crates.io
index. On a pull request its guard job runs pixi lock --check ahead of any environment
install, and when the lock has drifted the second job refreshes pixi.lock (and Cargo.lock,
when the plan vendors crates) onto the branch as pixi-sandbox[bot] with a conventional
chore(lock): commit, then dispatches --relock-ci-workflow explicitly — a GITHUB_TOKEN
push starts no workflow, so without that dispatch the lock commit would never be tested. It
solves with the pixi version pinned in the embedded tool catalogue, so the lockfile it writes
is one the pixi inside a transport can read. A fork pull request cannot be pushed to and the
job says so instead of failing obscurely.
Without --config, init prefers pixi-sandbox.toml, falls back to an existing
.pixi-sandbox.toml, and creates the preferred name when neither exists. Generated workflow and
launcher markers allow safe regeneration without --force; an unmarked user-owned destination
requires the explicit flag. The launcher uses only local git archive, tar, and the verified
binary stored under .pixi-sandbox/tools/<platform>/ in the sandbox branch. It never downloads
during restoration.
Every file init writes carries a pixi-sandbox-version: X.Y.Z line beside its ownership
marker, naming the exact CLI release that rendered it.
init --check
Section titled “init --check”Render every owned file fresh, write nothing, and report drift:
pixi-sandbox init --checkExits 0 when the generated workflow, the relock workflow, the launcher, and the sandbox
config schema all match what init would produce right now. Otherwise it names each finding
on its own line with the remedy that fixes it, and exits non-zero:
- a missing owned file — remedy:
pixi-sandbox init - a stale owned file, present and marked but no longer matching a fresh render — remedy:
pixi-sandbox init - a foreign file at an owned path, present but carrying no ownership marker — remedy:
pixi-sandbox init --force - a sandbox config whose
schemathis CLI no longer understands — remedy: upgrade the CLI (a config older than the CLI’s current schema is accepted silently; config is reviewed data and is never rewritten by eitherinitor--check)
--check and --force are mutually exclusive: auditing a tree and replacing a foreign file on
it are different operations on purpose. This is the same check the generated workflow’s
scheduled upgrade job runs against a freshly self-updated binary (see below) before it proposes
anything.
Validate pixi-sandbox.toml and emit matrix.
pixi-sandbox plan --config pixi-sandbox.tomlpixi-sandbox plan --config pixi-sandbox.toml --jsonRejects unsafe/implicit runner selection, checks embedded tool coverage.
pixi-sandbox tools listpixi-sandbox tools list --tools-lock /path/to/tools.lock.json
# connected host: is any pin stale? downloads nothing, exits non-zero if sopixi-sandbox tools update --check
# refresh the pins and print a candidate catalogue for reviewpixi-sandbox tools update > /tmp/tools.lock.json
# refresh one file in place (atomic; unchanged on any failure)pixi-sandbox tools update --tools-lock /path/to/tools.lock.jsonpixi-sandbox tools update --tool pixi --tools-lock /path/to/tools.lock.jsontools update resolves each tool’s latest release from the repository in its own
url_template, downloads the release assets, and re-derives the recorded linkage from the
bytes — a pin that claims static but ships a dynamically linked binary is refused. The write
happens only after every asset has been checked, so a failure leaves the file untouched.
Upstream does not publish checksums for the bare binaries this catalogue pins (see design.md
§10), so each pin’s note records which guarantees actually applied: cross-checked against an
upstream manifest, manifest-does-not-cover-this-asset, or no manifest. Treat a new pin as
reviewed data: read the diff before committing it.
self-update
Section titled “self-update”Replaces a standalone pixi-sandbox binary with a published release. Needs network; it is the one command that does.
# the latest release, replacing the running binarypixi-sandbox self-update
# an exact release — what every committed workflow and transport should namepixi-sandbox self-update --version 0.5.0
# roll back by naming an older releasepixi-sandbox self-update --version 0.4.3
# resolve and report, writing nothingpixi-sandbox self-update --check
# CI: update a disposable binary in runner scratch, not the machine's own toolpixi-sandbox self-update --version 0.5.0 --dest "$RUNNER_TEMP/pixi-sandbox"| flag | meaning |
|---|---|
--version X.Y.Z |
Install this exact release. Accepts X.Y.Z or vX.Y.Z; anything else is refused before any network access. Without it, the latest release is resolved. |
--check |
Print the current and resolved versions and exit. Downloads no binary and writes nothing. |
--dest PATH |
Update this path instead of the running binary. |
--repo OWNER/NAME |
Repository publishing the release assets. Defaults to Archont561/pixi-sandbox. |
What it verifies
Section titled “What it verifies”The order is the point: the release is resolved and the destination is classified before
anything is downloaded, so a destination this command will not touch costs no bytes. Then
SHA256SUMS is fetched before the binary — there is no reason to hold bytes you cannot
judge — and the asset is checked against it. A missing entry and a digest mismatch are
separate refusals, and neither reaches the disk. Nothing downloaded is ever executed.
The host is mapped to one canonical asset (pixi-sandbox-<target>, with .exe on Windows); an
unsupported host is an error naming the host rather than a guess.
What it refuses
Section titled “What it refuses”Self-update only ever replaces a standalone binary. If pixi-sandbox was installed by a package manager, the prefix records the file’s digest, and overwriting it in place would leave the environment describing bytes that are no longer there. So the destination is classified by where it lives, and each refusal names the remedy that actually works:
| destination | remedy |
|---|---|
Inside a conda/Pixi prefix (an ancestor conda-meta/) |
pixi update pixi-sandbox |
A pixi global install trampoline |
pixi global update pixi-sandbox |
| A pixi-sandbox-managed launcher in your user bin directory | restore a newer transport, or pass --dest |
A restored transport tool under .pixi/tools/<platform>/ |
repack and republish the transport |
There is no --force. An escape hatch here would reintroduce exactly the corruption the check
exists to prevent.
How the replacement is safe
Section titled “How the replacement is safe”The verified bytes are staged in the destination’s own directory — never a temp dir, so the final move is same-filesystem and therefore atomic — and only then swapped in.
On Unix the staged file is renamed straight over the destination. That is atomic, and it works
even when the destination is the binary currently running: the rename swaps the directory entry
and leaves the busy inode to the running process, so there is no window in which the tool is
missing and no Text file busy.
Windows cannot rename onto an existing file and will not delete a running image, but it does
allow renaming one. So the old binary is moved aside to a .pixi-sandbox-old-<version> sibling,
the new file takes its name, and the leftover is removed at the start of the next update —
deleting it immediately fails by design while the old image is still mapped. If the second
rename fails, the first is undone.
One-command reconstruction
Section titled “One-command reconstruction”pixi-sandbox init # connected host; commit the generated files./restore.sh # disconnected host; local Git objects, or one sandbox-branch fetch if connectedThe generated platform-appropriate launcher contains no installation logic. It archives the
configured sandbox branch and delegates verification and restoration to its nested binary; if the
ref is absent in a connected clone it fetches refs/heads/<branch> into origin/<branch>, while
PIXI_SANDBOX_FETCH=skip keeps a fully local airlock from attempting any remote access.