run() an agent, a sandbox provider, and a prompt. Sandcastle copies the repo into the sandbox, runs the agent, and merges the agent’s commits back into your current branch.
This page adds a Superserve sandbox provider to your project, so each run gets its own isolated VM. The agent’s API key stays in a Superserve secret and never enters the sandbox.
Prerequisites
- Node.js 22.18 or later, so
noderuns the.tsfiles directly - A git repo with at least one commit
- A Superserve API key. See API keys.
- An Anthropic API key for Claude Code, or an OpenAI API key for Codex
Setup
Sandcastle is a TypeScript library. Install it and the Superserve SDK in your repo:await, so set "type": "module" in package.json. Then put your keys in .env:
.env
.env and Sandcastle’s run files out of git. Sandcastle copies only committed files into the sandbox:
.gitignore
Store the API key as a secret
Store your Anthropic key as a team secret once. Superserve gives the sandbox a stand-in token and attaches the real key only on requests to Anthropic.pip install superserve and export the keys from .env before you run python create_secret.py.
Add the sandbox provider
Sandcastle’screateIsolatedSandboxProvider turns a create function into a sandbox provider. This provider creates a Superserve sandbox for each run, and sends Sandcastle’s commands and file copies to it. It tags each sandbox with the metadata sandcastle: "true", keeps a heartbeat tag up to date while the run uses the sandbox, and deletes the sandbox when the run ends. Save it as .sandcastle/superserve.ts:
.sandcastle/superserve.ts
superserve() takes the same options as Sandbox.create, such as fromTemplate, secrets, network, and metadata, plus:
The provider also sets
idleTimeoutSeconds to 10 minutes, unless you pass another value, so a sandbox with no command running stops billing. A paused sandbox can still belong to a run, for example while a host.onSandboxReady hook runs on your machine. The next command resumes it. If you pass timeoutSeconds, set it longer than any run. It pauses the sandbox even while a command runs, and Sandcastle then stops the run with an error.
Run Claude Code
Thesuperserve/claude-code template has Claude Code and git installed. Bind the secret to ANTHROPIC_API_KEY, and pass the agent and the prompt to run():
.sandcastle/main.ts
claude-sonnet-4-6 (trimmed):
main in this run. Sandcastle sets the sandbox’s git user.name and user.email from your repo, so the agent’s commits have your identity.
Run Codex
Thesuperserve/node-22 template has Node.js and git. Install Codex in a sandbox.onSandboxReady hook, which runs in the sandbox before the agent starts. Codex reads its key from CODEX_API_KEY when it runs in exec mode.
First, add your OpenAI key to .env as OPENAI_API_KEY, and store it as a secret with the OpenAI provider:
CODEX_API_KEY:
.sandcastle/codex.ts
gpt-5.5 (trimmed):
fromTemplate.
Delete leftover sandboxes
The provider deletes its sandbox when a run ends, also when the run fails. If the Node.js process stops before that, for example onkill -9, the sandbox stays.
The sandbox’s status does not tell you if a run still uses it. Use the heartbeat tag instead: while the run’s process is alive, the provider sets sandcastle-heartbeat to the current time every minute, also while the sandbox is paused. These scripts delete only tagged sandboxes with no heartbeat for 15 minutes, so they do not delete a sandbox that a run in another process or on a teammate’s machine still uses:
STALE_MS.
You can also pass autoDeleteSeconds to superserve(). Superserve then deletes a sandbox after it stays paused for that long, and it does not check the heartbeat. Set it longer than any pause during a run, such as a long host.onSandboxReady hook. See Lifecycle for these options, and Metadata tags for more about filters.
Good to know
- Sandcastle is TypeScript only. The Python SDK can create the secrets and delete leftover sandboxes, but it can’t run Sandcastle.
- The provider supports
run(). It does not supportinteractive(), because it does not implementinteractiveExec. - Commands in the sandbox run as root. Claude Code refuses
--dangerously-skip-permissionsas root unlessIS_SANDBOX=1is set, so the provider sets it in every sandbox. - Sandcastle copies the repo into the sandbox with
git bundle, so the template must havegit. Uncommitted changes and ignored files are not copied. - The agent’s output reaches Sandcastle line by line while it runs. If the agent prints nothing for 10 minutes, Sandcastle stops the run. Change this with the
idleTimeoutSecondsoption ofrun(). It is a different option from the sandbox’sidleTimeoutSecondsinsuperserve(). - The provider writes the prompt and copied directories to temporary files in the sandbox’s
/tmp, and deletes them after use. - To limit what the agent can reach, pass network rules as
network. Allow the agent’s API host, and any hosts that theonSandboxReadyhooks need, such as the npm registry.
Resources
Sandcastle
Agents, branch strategies, hooks, and the
run() options.Secrets
How secrets keep API keys out of the sandbox.