Run ad-hoc, heavy, or untrusted code off your machine — from inside Claude Code.
A Claude Code plugin that gives Claude a skill + 18 slash commands driving the authed createos CLI. Work runs in disposable CreateOS Sandboxes — roughly 200 ms from create to your first command — that self-destruct when done.
- Why offload?
- How it works
- Two patterns
- Install
- Requirements
- Command reference
- Egress control
- Heavy builds
- Uploads & excludes
- Shapes
- The skill
- Hooks
- Direct CLI cookbook
- Safety & scope
- Environment variables
- Troubleshooting
Heavy builds, flaky test suites, and untrusted code don't belong on your laptop:
- Untrusted code — a dependency's post-install script or an AI-generated snippet runs in a disposable Sandbox, not your shell. Egress can be locked to an exact allowlist.
- Heavy work —
npm ci,cargo build,pytest,go test, torch installs — keep your machine cool and free while a bigger box does the grind. - Parallel / matrix — split a test suite or a version matrix across N isolated boxes at once.
- Clean-room repros — reproduce a bug on a pristine Linux box with nothing of yours leaking in.
- Live dev loops — a reusable per-repo box with file sync, so a dev server or watcher inside the box reacts to your local edits.
Claude Code
│ slash command (/createos-sandbox:offload …) or the skill decides to offload
▼
scripts/cos ← this plugin's driver (a portable bash script)
│ wraps + hardens
▼
createos CLI ← authed control-plane client (auto-installs on first use)
│
▼
CreateOS Sandbox ← your code runs here, then the box is destroyed
The plugin is a thin Claude-facing surface over the createos CLI. It ships four things:
| Piece | Path | Role |
|---|---|---|
| Slash commands | commands/*.md |
18 commands (offload, fanout, shell, …), each a thin wrapper that calls scripts/cos |
| Skill | skills/using-createos-sandbox/SKILL.md + references/ |
teaches Claude when to reach for the sandbox on its own, with depth loaded on demand |
| Hooks | hooks/hooks.json + scripts/ |
SessionStart publishes the driver's absolute path; PreToolUse(Bash) nudges on heavy build/test commands |
| Driver | scripts/cos |
the actual logic — staging, egress, keepalive, sync, networking, lifecycle, state |
Slash commands invoke ${CLAUDE_PLUGIN_ROOT}/scripts/cos, which Claude Code expands when the command is loaded. The Bash tool's own environment does not carry that variable, so when the skill decides to offload on its own it needs a real path — that is what the SessionStart hook supplies. Without it the path collapses to /scripts/cos, and the observed failure mode is Claude quietly rebuilding the offload out of raw CLI primitives, losing egress restriction, keepalive, auto-destroy, and the auth preflight along the way. See ADR-0001.
1. One-shot offload (default, safe)
Stage a dir → exec → optionally pull artifacts → auto-destroy. One-way: box-side changes never touch local unless you ask (-o). Best for builds, tests, and untrusted code.
2. Live session (opt-in)
A reusable per-repo box + one-way file sync (default; -2 for two-way). A dev server or watcher inside the box reacts to your local edits. Best for interactive dev loops, long-running services, and networking.
From the marketplace (recommended):
/plugin marketplace add NodeOps-app/createos-claude-plugins
/plugin install createos-sandbox@createos
Dev (instant, no install):
claude --plugin-dir /path/to/createos-claude-plugins/createos-sandbox
/reload-plugins # after editing plugin filescos is not on PATH by default. To use bare cos in your own terminal, run its installer once by absolute path:
/path/to/createos-claude-plugins/createos-sandbox/scripts/cos install # symlinks to ~/.local/bin/cos${CLAUDE_PLUGIN_ROOT} only expands inside slash-command frontmatter — it is not set in your shell, nor in Claude's Bash tool environment. Slash commands resolve the path for you; for autonomous skill use the SessionStart hook publishes it. Running cos install once removes the question entirely.
- A CreateOS account. The
createosCLI auto-installs if missing — on first usecosruns the official one-liner (curl -sfL …/install.sh | sh -). Opt out withCOS_NO_AUTOINSTALL=1; override the install source withCOS_CLI_INSTALL_URL. - Sign in, one of two ways. Verify either with
cos auth.- Browser (recommended) — run
createos loginin your own terminal and pick "Sign in with browser". This is an interactive prompt, so Claude cannot run it for you; the OAuth session lands in~/.createos/.oauth. - API key —
export CREATEOS_API_KEY=<key>(grab one from the dashboard). Set it and browser login is skipped entirely — the right choice for CI, headless boxes, and devcontainers. Export it in the shell that launches Claude Code; never paste it into a Claude conversation, where it would be written to the transcript.
- Browser (recommended) — run
- Host tools:
jq,tar,bash,base64(required);perl(ANSI stripping / path resolution);shasum(falls back tosha1sum/sha256sum);curl(one-time CLI install only). - Optional per feature:
wg-quick+sudoforvpn; the currentcreateosCLI for sync--mode/--exclude(an old CLI falls back to two-way with a warning).
| Command | Summary |
|---|---|
offload [flags] <dir> <cmd> |
one-shot: stage → run (keepalive) → pull → destroy |
fanout [-j N] [flags] <dir> <cmd1> [cmd2] … |
run each command in its own throwaway box, in parallel |
shell [-s] [-r] [-e|-p|-E] |
instant throwaway interactive Linux (destroyed on exit) |
up [-s] [-r] [-n] [-e|-p|-E] |
create/reuse the per-repo project box |
run <cmd> |
exec in the project box (streamed, state persists) |
sync [-2|-M] [-x glob] <local-dir> [remote-dir] |
file sync into the project box (background) |
tunnel <remote> [local] |
forward a box port to 127.0.0.1 (private) |
expose <port> |
public HTTPS URL for a box port |
unexpose |
revoke the public URL / disable ingress |
cluster up <N> | run […] <cmd> | ls | down |
N boxes on one private network, name-addressable |
disk create | ls | show | attach | detach | rm |
BYO S3 bucket mounts on the project box |
vpn [register <name> | up] |
WireGuard L3 into your private networks |
fork |
snapshot the project box → independent clone |
pause · resume |
park the warm project box at zero compute cost / restore it exactly |
template submit <name> [-f Dockerfile] | ls | show | logs | rm |
build a custom rootfs so boxes boot pre-provisioned |
down |
stop sync/tunnels + destroy the project box (+ cluster) |
status |
show active box + sync + tunnels + cluster |
Flag order: flags (
-s/-r/-e/-p/-E/-o/-x/-w/-K) come before the positional<dir> <cmd>.
The core command. Stages a directory into a fresh box, runs a command, optionally pulls artifacts back, then destroys the box.
/createos-sandbox:offload [-p preset] [-e dom] [-E] [-x glob] [-o out] [-w GB] [-K] [-s shape] [-r rootfs] <dir> <cmd>
| Flag | Meaning |
|---|---|
-s <shape> |
box size (default s-1vcpu-1gb; see Shapes) |
-r <rootfs> |
root filesystem image (default devbox:1) |
-o <out> |
tar a box-side dir and pull it back to local |
-w <GB> |
attempt swap (see caveat under Heavy builds) |
-K |
keep the box on a real failure so you can inspect it |
-e <domain> |
allow one egress domain (repeatable) |
-p <preset> |
egress preset — python-uv | rust-cargo | npm | github (repeatable, composes with -e) |
-E |
unrestricted egress (trusted offload) |
-x <glob> |
extra upload exclude (repeatable) |
# Run a test suite, pull nothing, box auto-destroys
/createos-sandbox:offload . "npm ci && npm test"
# Python build with locked egress, pull the dist/ folder back
/createos-sandbox:offload -p python-uv -o dist . "uv sync --frozen && uv run python -m build"Keepalive: long or quiet compiles no longer die to exec-stream idle resets — the command runs detached with a heartbeat and re-attaches if the stream drops, so the build (and its cache) survives.
Run each command in its own throwaway box, in parallel, then collect results. Ideal for splitting a test suite or a version matrix.
/createos-sandbox:fanout [-j N] [-p preset] [-s shape] [-r rootfs] [-e dom] [-E] [-x glob] <dir> <cmd1> [cmd2] …
-j N— max concurrent boxes (default 2, the external-key running-quota). Other flags mirroroffload.- Exit codes are aggregated across boxes; each box is fully isolated from the others.
/createos-sandbox:fanout -j 2 -p python-uv . "pytest -q tests/a" "pytest -q tests/b" "pytest -q tests/c"An instant, interactive Linux box that is destroyed on exit.
/createos-sandbox:shell [-s shape] [-r rootfs] [-e dom | -p preset | -E]
Interactive — it takes over the terminal, so it can't run as a foreground agent command. Run it yourself: type
!cos shellin the Claude prompt (needscos installfirst, or use the full path). Egress is unrestricted by default likeoffload;-e/-prestrict it.
A reusable, per-repo box addressed by your working directory. up creates it (or adopts an existing running one); run execs in it with persistent state; sync streams files in; down tears it all down.
/createos-sandbox:up [-s shape] [-r rootfs] [-n name] [-e dom | -p preset | -E]
/createos-sandbox:run <cmd>
/createos-sandbox:sync [-2 | -M] [-x glob] <local-dir> [remote-dir]
/createos-sandbox:down
/createos-sandbox:status
upis idempotent — run it again on the same repo and it reuses the same box instead of creating a new one.runstreams output and keeps box-side state between calls (installed deps, running processes).syncis one-way by default (laptop → box).-2= two-way (box writes flow back),-M= mirror (deletes box-side extras),-x <glob>= exclude. First run downloads Mutagen (~60–90 s before edits propagate). Install deps inside the box (cos run 'npm ci') rather than syncingnode_modulesup — big/regenerable dirs are excluded by default.pause/resumepark the box at zero compute cost and bring it back exactly — see Pause, resume, and custom images. Preferpauseoverdownwhen the box has a warm toolchain you'll want again.downstops sync + tunnels and destroys the project box (and any cluster). The next session starts from scratch.statusshows the active box + sync + tunnels + cluster.
/createos-sandbox:up
/createos-sandbox:run "npm ci"
/createos-sandbox:sync . /work # local edits stream into /work
/createos-sandbox:run "npm run dev &"
/createos-sandbox:down| Command | What it gives you |
|---|---|
tunnel <remote> [local] |
Reach a box-side service on your laptop. Run a dev server in the box, then tunnel 3000 → http://127.0.0.1:3000. Private, background, no public URL. Stopped by down. |
expose <port> |
A public HTTPS link for a port — <id>-<port>.app.sb.createos.sh, stable for the box's lifetime. The service must bind 0.0.0.0. Anyone with the link can reach it. Revoke with unexpose. |
unexpose |
Revoke the public URL / disable ingress on the active box. |
cluster up <N> |
N boxes on one private network, reaching each other by fully-qualified name (curl http://cos-cl-<key>-2.fc.local:8080 — the bare short name is NXDOMAIN). cluster run -a '<cmd>' fans a command across all; cluster run <name|idx> '<cmd>' targets one. cluster ls / cluster down manage them. For distributed-system / DB-replication / p2p / load-test repros. Counts against quota — keep N small. |
vpn register <name> then vpn up |
Join your laptop to the whole private network over WireGuard (reach every sandbox by name/IP). vpn up needs wg-quick + sudo and blocks until Ctrl-C — run it in your own terminal (!cos vpn up). |
fork |
Snapshot the warm project box → an independent clone for matrix/parallel experiments. The fork is self-managed. |
/createos-sandbox:pause
/createos-sandbox:resume
/createos-sandbox:template submit <name> [-f Dockerfile] | ls | show <name> | logs [-f] <name> | rm <name>
pause is the cheap way to end a session. It snapshots the whole box — disk, memory, and running processes — frees the host, and stops compute billing, so a box with a warm toolchain costs nothing while parked. resume restores it exactly — files, installed dependencies, and processes that were running when it paused. End to end through the CLI both operations land around 6–8 s (measured), and resume is slower when the snapshot has to be pulled to a different host than the one it was taken on. down, by contrast, destroys the box and forces the next session to reinstall everything.
pause stops the file sync and any tunnels first, since a paused box serves no traffic. Re-run sync / tunnel / expose after resuming. The project box also carries a 30-minute idle auto-pause as a backstop; raise it with createos sandbox edit <id> --auto-pause 4h when a box is serving an exposed URL that people will hit intermittently, or the demo will look dead between visitors.
template bakes a toolchain into an image so boxes boot pre-provisioned instead of running the same install prelude on every offload. The build service enforces constraints that would otherwise only surface as a rejection after upload, so cos preflights the Dockerfile locally: exactly one FROM (single-stage), an operator-allowlisted base written literally (no ARG substitution), no COPY/ADD (no build context is uploaded — fetch inside a RUN), and 64 KiB of source at most. Two builds run concurrently per account.
/createos-sandbox:template submit myimage -f Dockerfile
/createos-sandbox:up -r myimage # or: offload -r myimage . "<cmd>"A custom image is slow on its first boot on each host (that host has to fetch it) and fast on every boot after. The built-ins — devbox:1, ubuntu:26.04, debian:13, alpine:3.20 — are kept warm and never pull; createos sandbox rootfs lists them.
Mount your own S3 bucket into the project box.
/createos-sandbox:disk create <name> --bucket <b> --endpoint <url> --access-key <k> --secret-key <s>
/createos-sandbox:disk ls
/createos-sandbox:disk show <name>
/createos-sandbox:disk attach <disk> <mount>
/createos-sandbox:disk detach
/createos-sandbox:disk rm
/createos-sandbox:disk create data --bucket my-bucket --endpoint https://s3.amazonaws.com --access-key … --secret-key …
/createos-sandbox:disk attach data /mnt/dataBy default, egress is unrestricted — the box can reach any host, and cos prints a one-line ⚠ egress UNRESTRICTED notice. To isolate an untrusted build, restrict outbound to an exact set:
-e <domain>— allow one domain (repeatable).-p <preset>— a curated bundle of the registries + CDNs a build actually reaches (repeatable, composes with-e).-E— force explicitly unrestricted (silences the warning for trusted offloads).
| Preset | Hosts allowed |
|---|---|
python-uv |
astral.sh, releases.astral.sh, pypi.org, files.pythonhosted.org |
rust-cargo |
crates.io, static.crates.io, index.crates.io, static.rust-lang.org, cdn.pyke.io |
npm |
registry.npmjs.org |
github |
github.com, objects.githubusercontent.com, raw.githubusercontent.com, codeload.github.com |
cdn.pyke.iois included inrust-cargobecauseort-sys(ONNX Runtime) fetches from it. Compose presets and add stragglers with-e <host>:/createos-sandbox:offload -p python-uv -p rust-cargo -e cdn.example.com . "…"
How the allowlist is enforced. Rules can be a domain, *.domain, an IP, a CIDR, or any of those with a :port, and the list is allow-only — there is no deny token, so every destination a job needs has to be enumerated. Enforcement is not uniform, and the difference matters when blocking exfiltration is the actual goal:
- IP and CIDR rules apply immediately and cannot be bypassed from inside the box.
- Domain rules take roughly 30 seconds to apply after being set, and are a strong control for HTTPS traffic but a weak one for cleartext HTTP.
So a domain allowlist is the right control for "this build should only reach pypi and crates.io", and IP/CIDR rules are the right control for an adversarial workload. DNS keeps resolving for blocked destinations — a blocked connection fails as a connection error (e.g. curl exit 35), not a name-resolution failure, so checking whether DNS works will mislead you when debugging.
- Egress: default unrestricted; use
-p/-eto lock it down (see above). - Keepalive: long/quiet compiles run detached with a heartbeat and survive stream drops.
-Kkeeps the box on a real failure for inspection. - Excludes:
.git/target/node_modules/__pycache__/.venv/media are excluded from the upload by default;-x <glob>adds more. - Swap caveat:
-w <GB>attempts swap, butdevbox:1can'tswapontoday — so a torch/maturin build on a small box may OOM/ENOSPC. Install only the extra/group you need (e.g.uv sync --group dev) rather than--all-extras. - Shape rejection: a shape your account can't use fails with a clean
Allowed: [...]list — pick from it. - Bandwidth: each box starts with a fixed allowance for traffic it initiates (5 GiB by default). A large model download or
docker pullcan exhaust it, after which outbound traffic stops. Top it up additively viacreateos sandbox edit <id>; the exec, file-transfer, and tunnel channels stay reachable regardless.
Uploads (offload/fanout) and one-way sync skip big/regenerable dirs by default so you don't ship node_modules over the wire:
.git target node_modules __pycache__ .venv .mypy_cache .pytest_cache
.gradle .cargo/registry dist build .next .turbo *.gif *.mp4 *.mov *.zst
Add more with -x <glob> (repeatable). Install dependencies inside the box (cos run 'npm ci') rather than syncing them up.
- Default shape:
s-1vcpu-1gb. Default rootfs:devbox:1. - Pick a bigger box with
-s <shape>. A shape your account can't use is rejected with anAllowed: [...]list — pick from it rather than guessing. - Discover what you can boot:
createos sandbox shapes(sizes) andcreateos sandbox rootfs(images).
using-createos-sandbox teaches Claude when to offload — untrusted code, heavy builds/tests, parallel/matrix work, clean-room repros, live dev loops — so it reaches for the sandbox on its own instead of grinding on your laptop. It drives the same scripts/cos helper the slash commands use.
SKILL.md stays a decision surface; the depth lives in skills/using-createos-sandbox/references/ and is loaded only when a task needs it:
| Reference | Covers |
|---|---|
offload-and-egress.md |
offload flag table, egress presets and enforcement caveats, fanout, upload excludes, heavy-build OOM/disk/bandwidth traps |
networking.md |
choosing between tunnel/expose/cluster/vpn, cluster DNS names, expose gotchas, WireGuard setup |
lifecycle-and-images.md |
pause/resume, auto-pause tuning, fork caveats, built-in rootfs vs custom templates, env vars, remote editor, self-terminating jobs, single-file transfer, measured timings |
SessionStart(scripts/session-start.sh) publishes the driver's absolute path into Claude's context. This is what makes autonomous, skill-driven offload work at all — see How it works and ADR-0001. It writes nothing to disk.PreToolUse(Bash)(scripts/offload-hint.sh) watches for heavy build/test commands (npm ci,make,pytest,go test,cargo build,pip install, …) and adds a one-line nudge to consider/createos-sandbox:offload. The command still runs — the hook only suggests — and it skips sandbox/git/docker commands. Silence it withCOS_NO_HINT=1.
cos works standalone, no Claude required:
cos install # symlink onto PATH (once); createos CLI auto-installs on first use
cos offload -p python-uv . 'uv sync --frozen --group dev && uv run pytest -q'
cos offload -p python-uv -p rust-cargo -x target -o dist . 'uv sync --frozen && uv run pytest -q'
cos up && cos run 'npm ci' && cos sync ~/app /work # reusable box + one-way sync
cos fanout -j 2 -p python-uv . 'pytest tests/a' 'pytest tests/b' # parallel, isolated boxes
cos shell # instant throwaway Linux (destroyed on exit)
cos run 'npm run dev &' && cos tunnel 3000 # dev server → http://127.0.0.1:3000
cos expose 8080 # public HTTPS URL for port 8080
cos cluster up 3 && cos cluster run -a 'hostname' # 3 boxes, one private net
cos disk create data --bucket my-b --endpoint https://s3.amazonaws.com --access-key … --secret-key …
cos disk attach data /mnt/data # mount S3 into the project box
cos fork # snapshot → independent clone
cos template submit myimage -f Dockerfile # bake a toolchain into a reusable image
cos pause # park the warm box at zero compute cost
cos resume # bring it back exactly as it was
cos down # stops sync/tunnels, destroys box + cluster- One-way by default.
syncand offload uploads are laptop → box; box-side writes never touch local unless you opt into-2(two-way) or pull withoffload -o. Use-2deliberately — never casually on a repo root.-M(mirror) additionally deletes box-side extras. - Scoped statefile.
cosonly ever touches boxes it created (cos-*) or the project box recorded in its statefile. Your other sandboxes are never touched. State lives at${COS_STATE_DIR:-${XDG_CACHE_HOME:-~/.cache}/createos-sandbox}/<project>.json(plus a.tunnelssidecar). - Quota. External keys have been observed to allow 2 boxes running at once, with a daily creation cap. Neither is published policy — treat them as observed behaviour, budget
cluster/fanoutagainst them, and expect excess jobs to queue rather than fail. - Ending a session.
pausekeeps the box (and its warm state) at zero compute cost;downdestroys it. Either is fine — leaving a box running is not. - Untrusted code. Restrict egress (
-p/-e) so a malicious dependency can't exfiltrate or phone home.
| Variable | Effect |
|---|---|
CREATEOS_API_KEY |
sign in without browser login (CI / headless / devcontainers); verify with cos auth |
COS_NO_AUTOINSTALL=1 |
don't auto-install the createos CLI |
COS_CLI_INSTALL_URL |
override the CLI install-script source |
COS_CLI |
use a specific createos binary instead of the one on PATH |
COS_STATE_DIR |
override the statefile directory (default ${XDG_CACHE_HOME:-~/.cache}/createos-sandbox) |
COS_NO_HINT=1 |
silence the auto-suggest hook |
cos: not signed in to CreateOS— runcreateos loginin your own terminal (not via Claude — it needs a TTY), orexport CREATEOS_API_KEY=<key>. Verify withcos auth.createos: command not found— the auto-install landed outsidePATH; add~/.local/binto it and retry.cos: command not found— runcos installby its full path (theSessionStarthook prints that path into Claude's context), or callcosby full path. Do not type${CLAUDE_PLUGIN_ROOT}into a Bash command — that variable is unset in the Bash tool's environment and the path resolves to/scripts/cos./scripts/cos: no such file— same cause. Never work around it by rebuilding the offload from rawcreateos sandbox create/push/exec: that drops egress restriction, keepalive, auto-destroy, and the auth preflight.- Shape rejected — run
createos sandbox shapesand pick from theAllowed: [...]list in the error. - Box says paused /
cos runrefuses — the 30 m idle auto-pause fired, or someone ranpause.cos resumebrings it back; raise the timeout withcreateos sandbox edit <id> --auto-pause 4h. - Template build rejected —
cos template submitpreflights single-stage/no-COPY/64 KiB locally; a rejection past that is usually a base image outside the operator allowlist. - Build can't reach a host — you restricted egress; add the host with
-e <domain>or the right-p <preset>. synccopiednode_modulesanyway — yourcreateosCLI is old and lacks--exclude; upgrade it (coswarns when this happens).- Build OOM/ENOSPC on a small box — bump
-s <shape>;-wswap doesn't work ondevbox:1. Install only the deps you need. - Public
exposeURL returns nothing — the service must bind0.0.0.0, not127.0.0.1.
Part of the createos Claude Code plugin marketplace · createos.sh