Sitelet https://github.com/NodeOps-app/createos-plugins/tree/main/packages/claude-code-plugin
Skip to content

Latest commit

 

History

History

README.md

createos-sandbox

Run ad-hoc, heavy, or untrusted code off your machine — from inside Claude Code.

A Claude Code plugin that gives Claude a skill + 21 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.

Claude Code CreateOS Version


Contents

Why offload?

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.

How it works

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 22 commands (offload, exec, 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.

Two patterns

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.

Install

From the marketplace (recommended):

/plugin marketplace add NodeOps-app/createos-plugin
/plugin install createos-sandbox@createos

Dev (instant, no install):

claude --plugin-dir /path/to/createos-plugin/packages/claude-code-plugin
/reload-plugins      # after editing plugin files

Put cos on your PATH (optional but recommended)

cos is not on PATH by default. To use bare cos in your own terminal, run its installer once by absolute path:

/path/to/createos-plugin/packages/claude-code-plugin/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.

Requirements

  • A CreateOS account. At session start the SessionStart hook runs cos setup, which installs the createos CLI with the official one-liner (curl -sfL …/install.sh | sh -) if missing, and otherwise re-runs it in the background to upgrade in place (same directory, never sudo). Opt out with COS_NO_AUTOINSTALL=1; override the install source with COS_CLI_INSTALL_URL. A binary set via COS_CLI is never auto-installed. Re-run cos setup by hand any time.
  • Sign in, one of two ways. Verify either with cos auth.
    • Browser (recommended) — if createos whoami says you are signed out and tmux is available, cos setup starts createos login in a hidden tmux session (createos-login), picks "Sign in with browser", and your default browser opens the CreateOS sign-in page — finish it there (a fallback URL is printed if no browser opens). Without tmux, run createos login in your own terminal and pick "Sign in with browser". The OAuth session lands in ~/.createos/.oauth.
    • API key — export CREATEOS_API_KEY=<key> (grab one from the dashboard). Set it and browser sign-in 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. To store a token via the CLI instead, tmux kill-session -t createos-login, then run createos login yourself and pick "Sign in with API token".
  • Host tools: jq, tar, bash, base64 (required); perl (ANSI stripping / path resolution); shasum (falls back to sha1sum/sha256sum); curl (CLI install/upgrade); tmux (optional, automatic browser sign-in).
  • Optional per feature: wg-quick + sudo for vpn; the current createos CLI for sync --mode/--exclude (an old CLI falls back to two-way with a warning).

Command reference

Command Summary
offload [flags] <dir> <cmd> one-shot: stage → run (keepalive) → pull → destroy
exec [-l lang] [-i stdin] [-t secs] [-N] <file|-> [args] run one source file (untrusted/ad-hoc) in a throwaway box
fanout [-j N] [flags] <dir> <cmd1> [cmd2] … run each command in its own throwaway box, in parallel
agent [flags] <agent> <dir> <prompt> run claude/codex/opencode/pi/cursor on your code in a box
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
desktop [-s shape] [-S screen-N] graphical desktop:1 box + a live noVNC URL
computer <op> drive that desktop — screenshot, click, type, key, open, windows
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/-v/-K) come before the positional <dir> <cmd>.

Offload — one-shot

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 | openrouter | openai | anthropic | cursor (repeatable, composes with -e)
-E unrestricted egress (trusted offload)
-x <glob> extra upload exclude (repeatable)
-v KEY[=VAL] declare an env var in the box (repeatable). Bare KEY forwards the value from your own shell, so a secret stays out of shell history and the Claude transcript
# 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"

Exec — remote code execution

Run untrusted code or any ad-hoc script — anything you would rather not run locally — as one source file in a throwaway box. No directory to stage.

/createos-sandbox:exec [-l lang] [-i stdin-file] [-t secs] [-N] [-p preset] [-e dom] <file> [args...]

Languages py js mjs cjs ts go sh rb c cpp rs, picked from the extension or -l. -i feeds stdin, -t caps wall-clock time (default 120 s, exit 124), -N denies all egress (default is unrestricted, like offload). stdout/stderr and the exit code are the program's own.

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.

Fanout — parallel boxes

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 mirror offload.
  • 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"

Shell — throwaway Linux

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 shell in the Claude prompt (needs cos install first, or use the full path). Egress is unrestricted by default like offload; -e/-p restrict it.

Project box — live sessions

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
  • up is idempotent — run it again on the same repo and it reuses the same box instead of creating a new one.
  • run streams output and keeps box-side state between calls (installed deps, running processes).
  • sync is 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 syncing node_modules up — big/regenerable dirs are excluded by default.
  • pause/resume park the box at zero compute cost and bring it back exactly — see Pause, resume, and custom images. Prefer pause over down when the box has a warm toolchain you'll want again.
  • down stops sync + tunnels and destroys the project box (and any cluster). The next session starts from scratch.
  • status shows 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

Networking

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.

Desktop and computer use

Some work needs a screen — a real (not headless) browser, a GUI app, an installer that only exists as a wizard. desktop puts the project box on the desktop:1 rootfs (XFCE, Google Chrome, xdotool/wmctrl/scrot/xclip) and hands back a live noVNC URL; computer drives that same desktop from the agent side.

cos desktop                          # desktop:1 box + ingress + noVNC URL (waits for the desktop to boot)
cos computer screen                  # {"width":1280,"height":800} — the coordinate space
cos computer screenshot              # PNG → prints a path (Claude opens it with Read)
cos computer open https://example.com
cos computer click 640 400
cos computer type 'hello'
cos computer key ctrl l              # a chord
cos computer help                    # every op, plus `raw` for the rest of the API

The two halves compose: the URL lets you watch and take over in a browser while Claude acts through computer. desktop:1 ships the same five agent CLIs devbox:1 does, so "run an agent on a box and watch its screen" needs no extra setup — see Coding agents.

Gotcha Detail
The link is a bearer Anyone holding the noVNC URL can drive the desktop, and its token expires (the command prints when). Re-running desktop mints a fresh one and invalidates the old link for new connections.
Boots late The desktop stack starts after the box reports running. cos desktop polls for readiness; a bare cos up -r desktop:1 does not.
409 is ambiguous The control plane returns desktop_unavailable both while the desktop is coming up and when an action fails on a live desktop. Not a signal that the box is broken.
Raw pixels Coordinates are unscaled X11 pixels of that screen. Read the bounds from cos computer screen.
Needs ingress desktop enables it for you. unexpose turns it off and kills the link.

desktop and computer are the only cos commands that call the CreateOS REST API directly — the createos CLI has no computer or desktop command yet. Everything else shells out to the CLI as usual. Auth is reused as-is: CREATEOS_API_KEY or ~/.createos/.token go out as X-Api-Key, a browser session's JWT as X-Access-Token.

/createos-sandbox:desktop  [-s shape] [-S screen-N]
/createos-sandbox:computer screenshot | screen | cursor | move <x> <y> | click [<x> <y>]
                           | type <text> | key <k>… | open <url> | windows | raw <M> <path> [json]

Pause, resume, and custom images

/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; set it at create time with cos up -P 22h (max 24h), or on a live box 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 24.04, batteries included), ubuntu:26.04, debian:13, alpine:3.20 — are kept warm and never pull; createos sandbox rootfs lists them.

Disks — BYO S3

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/data

Coding agents

devbox:1 ships five agent CLIs, so running one in a box needs no install step:

CLI Version seen Third-party providers
claude 2.1.260 OpenRouter, Anthropic, any Anthropic-compatible endpoint
codex 0.153.4 OpenRouter, OpenAI, any Responses-compatible endpoint
opencode 1.18.30 anything — catalog providers or a custom base URL
pi 0.85.1 anything — built-in providers or a custom base URL
cursor-agent 2026.09.10 none — Cursor's own service only

desktop:1 ships the same five.

/createos-sandbox:agent [-P provider] [-m model] [-k KEYVAR] [-o .] [-p preset] [-s shape] <agent> <dir> <prompt>
export OPENROUTER_API_KEY=sk-or-...
/createos-sandbox:agent -m openai/gpt-5.6-luna -o . claude . "fix the failing tests"
/createos-sandbox:agent -P anthropic -m claude-sonnet-4-5 -p anthropic -o . pi . "add type hints"
/createos-sandbox:agent -P https://gw.example.com/v1 -k MY_KEY -o . codex . "port this to v2"

It stages the directory, wires the agent to the provider, runs it headless with permission gates off — the microVM is the isolation — pulls the edits back with -o ., and destroys the box. -P defaults to openrouter; -k names the host env var holding the key (default OPENROUTER_API_KEY / ANTHROPIC_API_KEY / OPENAI_API_KEY / CURSOR_API_KEY). Every offload flag works too.

Gotcha Detail
-o . or it's gone Without it the box is destroyed with the agent's edits inside. Run on a clean tree so git diff shows what changed.
Key comes from your shell -v/-k forward the value out of the launching shell. Never paste a provider key into a Claude conversation — it lands in the transcript.
openrouter.ai/api ≠ /api/v1 The first is Anthropic-compatible (what claude needs), the second OpenAI-shaped (what codex and opencode need). Wrong one gives an unhelpful 404.
codex is Responses-only wire_api = "chat" is rejected at config load, so a plain Chat-Completions gateway (vLLM, LiteLLM in chat mode) cannot drive it.
cursor-agent can't be repointed Its --endpoint/CURSOR_API_ENDPOINT expects Cursor's own protocol; BYO-key is an IDE-chat-only feature.
claude needs IS_SANDBOX=1 The box is root, and --dangerously-skip-permissions refuses to run as root without it. cos agent sets it.

Full per-agent env blocks and the wire-protocol matrix live in references/coding-agents.md.

Egress control

By 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
openrouter openrouter.ai
openai api.openai.com
anthropic api.anthropic.com, statsig.anthropic.com
cursor the *.cursor.sh / downloads.cursor.com set Cursor CLI needs

cdn.pyke.io is included in rust-cargo because ort-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.

Heavy builds

  • Egress: default unrestricted; use -p/-e to lock it down (see above).
  • Keepalive: long/quiet compiles run detached with a heartbeat and survive stream drops. -K keeps 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, but devbox:1 can't swapon today — 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 pull can exhaust it, after which outbound traffic stops. Top it up additively via createos sandbox edit <id>; the exec, file-transfer, and tunnel channels stay reachable regardless.

Uploads & excludes

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.

Shapes

  • 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 an Allowed: [...] list — pick from it rather than guessing.
  • Discover what you can boot: createos sandbox shapes (sizes) and createos sandbox rootfs (images).

The skill

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
docs.md index of every live CreateOS Sandbox docs page as a raw .md URL, so Claude fetches the current REST / SDK / CLI reference on demand instead of carrying it

Hooks

  • 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 also runs cos setup (CLI install/upgrade, browser sign-in if signed out) and adds the resulting CLI version/path and sign-in status to Claude's context.
  • 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 with COS_NO_HINT=1.

Direct CLI cookbook

cos works standalone, no Claude required:

cos install                                   # symlink onto PATH (once); createos CLI auto-installs on first use
cos setup                                     # install/upgrade the createos CLI; start browser sign-in if signed out
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 desktop && cos computer open https://example.com # graphical box + noVNC URL, then drive it
cos computer screenshot                              # PNG of the desktop → prints a path
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

Safety & scope

  • One-way by default. sync and offload uploads are laptop → box; box-side writes never touch local unless you opt into -2 (two-way) or pull with offload -o. Use -2 deliberately — never casually on a repo root. -M (mirror) additionally deletes box-side extras.
  • Scoped statefile. cos only 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 .tunnels sidecar).
  • 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/fanout against them, and expect excess jobs to queue rather than fail.
  • Ending a session. pause keeps the box (and its warm state) at zero compute cost; down destroys 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.

Environment variables

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

Troubleshooting

  • cos: not signed in to CreateOS — run cos setup (starts browser sign-in in a hidden tmux session), or createos login in your own terminal (not via Claude — it needs a TTY), or export CREATEOS_API_KEY=<key>. Verify with cos auth.
  • Browser sign-in never opened — use the fallback URL cos setup printed, or tmux kill-session -t createos-login and run createos login yourself.
  • createos: command not found — the auto-install landed outside PATH; add ~/.local/bin to it and retry.
  • cos: command not found — run cos install by its full path (the SessionStart hook prints that path into Claude's context), or call cos by 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 raw createos sandbox create/push/exec: that drops egress restriction, keepalive, auto-destroy, and the auth preflight.
  • Shape rejected — run createos sandbox shapes and pick from the Allowed: [...] list in the error.
  • Box says paused / cos run refuses — the 30 m idle auto-pause fired, or someone ran pause. cos resume brings it back; raise the timeout with cos up -P 22h at create time or createos sandbox edit <id> --auto-pause 4h on a live box.
  • Template build rejected — cos template submit preflights 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>.
  • sync copied node_modules anyway — your createos CLI is old and lacks --exclude; upgrade it (cos warns when this happens).
  • Build OOM/ENOSPC on a small box — bump -s <shape>; -w swap doesn't work on devbox:1. Install only the deps you need.
  • Public expose URL returns nothing — the service must bind 0.0.0.0, not 127.0.0.1.

Part of the createos Claude Code plugin marketplace · createos.sh