Sandbox profiles and setup scripts
A sandbox profile is a setup script that runs in a new sandbox after your repository is cloned and before the agent starts. Use one when every session needs something the sandbox does not have yet: dependencies installed, a database running, a private registry signed in to, a tool on the PATH.
The agent starts only once the script has finished, so it begins work in a sandbox that is already set up.
Where profiles live, and which one runs
Profiles are kept at three levels, each under Sandbox profiles in its settings: your organization, a workspace, and a project. When a sandbox launches without a choice, the narrowest level that has a default wins:
- the project's default, then
- the workspace's default, then
- the organization's default.
A level's default is the profile marked as default there — or, when a level has exactly one profile, that profile. With two or more unmarked profiles a level has no default and the next level up is used. If no level has one, no setup script runs.
To choose for one launch, open Advanced settings in the launcher and pick a profile, or None for no setup script. A restart keeps the choice its launch made. Automations that start sandboxes on your behalf can name a profile in their own settings.
Restarts and automations fall back to the default when the profile they chose has been archived, and the launch timeline says so. A launch you start yourself with an archived profile is refused, so the choice is never silently changed under you.
Writing a setup script
What the script runs in
- Ubuntu 22.04, as the sandbox user, with
sudowithout a password. - In the repository folder (your home folder when the project has no repository).
- Preinstalled: git, curl, jq, a C/C++ build toolchain, Python 3.10 with
venvandpip, Node.js 22 and 24 (a.nvmrc,.node-versionorengines.nodepicks one;corepackis enabled for pnpm and yarn), Go, and a stable Rust toolchain. - Bash. Start with
set -euo pipefailso the first failing command stops the script and is reported, instead of carrying on.
Showing progress
Print ::step:: followed by a name to say what the script is doing:
echo "::step::Install dependencies"
The step and the latest output show while the sandbox is setting up, and every step is recorded on the launch timeline.
Handing values to the agent
The script's own environment variables do not reach the agent. To hand something over, append to two files:
| File | Append | Example |
|---|---|---|
$PS_ENV | NAME=value lines — environment variables for the agent's commands | echo "DATABASE_URL=postgres://localhost/app" >> "$PS_ENV" |
$PS_PATH | one directory per line — added to the agent's PATH | echo "$PWD/.venv/bin" >> "$PS_PATH" |
Only the names are recorded on the launch timeline, never the values.
Services
Start services in the background (for example redis-server --daemonize yes or
sudo service postgresql start). They keep running after the script ends, and the agent can use
them.
Secrets
Add a secret reference to the profile (the Secrets section of the profile), naming a secret you have already stored. At launch it is looked up for the project being launched and given to the script as an environment variable.
- A profile's secrets reach the setup script only — not the agent.
- Their values are masked in the script's output.
- A
$PS_ENVline containing a secret's value is refused, so a secret cannot be handed to the agent by accident. - Anything the script writes to a file stays in the sandbox, where the agent can read it. Keep credentials in temporary files and remove them before the script ends — the private registry example shows how.
When the script fails
If setup fails decides what a failing script, a timeout or a secret that does not resolve does to the launch:
- Continue with a warning (the default): the agent starts anyway, and the session shows a warning naming the step that failed.
- Fail the launch: the launch stops and reports the failing step and exit code.
The timeout is 5 minutes unless you change it, up to 60. A script that installs large packages may need more.
To find out what went wrong, open the launch timeline: see A launch that will not finish.
Examples
Each example runs as it is in a sandbox; copy it and adjust it to your project. The same examples are beside the script editor, under Examples.
Start here
The shape every script shares: stop on the first error, name each step, and hand values and PATH entries to the agent.
#!/usr/bin/env bash
# Runs as the sandbox user, in the repository folder, before the agent starts.
# Stop at the first failing command so a broken setup is reported, not ignored.
set -euo pipefail
echo "::step::Check the toolchain"
node --version
python3 --version
echo "::step::Prepare the workspace"
mkdir -p "$HOME/.cache/my-project"
# Hand values to the agent: NAME=value lines go to $PS_ENV,
# extra PATH directories go to $PS_PATH.
echo "APP_ENV=development" >> "$PS_ENV"
echo "$HOME/.cache/my-project/bin" >> "$PS_PATH"
Node.js
Installs dependencies with pnpm, yarn or npm — whichever the lockfile names — and runs the build script if there is one.
#!/usr/bin/env bash
# Node.js project: install dependencies with the package manager the lockfile names.
# Node 22 and 24 are preinstalled; a .nvmrc, .node-version or "engines.node" picks one.
set -euo pipefail
# Let corepack fetch pnpm or yarn without asking; nobody can answer a prompt here.
export COREPACK_ENABLE_DOWNLOAD_PROMPT=0
echo "::step::Install dependencies"
if [ -f pnpm-lock.yaml ]; then
corepack pnpm install --frozen-lockfile
elif [ -f yarn.lock ]; then
corepack yarn install --immutable
elif [ -f package-lock.json ]; then
npm ci
elif [ -f package.json ]; then
npm install
else
echo "No package.json here; nothing to install."
fi
echo "::step::Build"
if [ -f package.json ] && jq -e '.scripts.build' package.json > /dev/null; then
npm run build
fi
Python
Creates a virtual environment in the repository and hands it to the agent, so its python and tools
are the ones on the PATH.
#!/usr/bin/env bash
# Python project: a virtual environment in the repository, handed to the agent.
set -euo pipefail
echo "::step::Create the virtual environment"
python3 -m venv .venv
.venv/bin/pip install --quiet --upgrade pip
echo "::step::Install dependencies"
if [ -f requirements.txt ]; then
.venv/bin/pip install --quiet -r requirements.txt
fi
if [ -f pyproject.toml ]; then
.venv/bin/pip install --quiet -e .
fi
# The agent's commands use this environment's python and tools.
echo "VIRTUAL_ENV=$PWD/.venv" >> "$PS_ENV"
echo "$PWD/.venv/bin" >> "$PS_PATH"
System packages and tools
Installs Ubuntu packages and a pinned command-line tool. ~/.local/bin is already on the agent's PATH.
#!/usr/bin/env bash
# System packages and extra command-line tools. The sandbox runs Ubuntu 22.04 and the
# script can use sudo without a password.
set -euo pipefail
echo "::step::Install system packages"
sudo apt-get update -qq
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends \
postgresql-client redis-tools graphviz
echo "::step::Install a pinned command-line tool"
# ~/.local/bin is already on the agent's PATH.
mkdir -p "$HOME/.local/bin"
curl -fsSL -o "$HOME/.local/bin/yq" \
"https://github.com/mikefarah/yq/releases/download/v4.44.3/yq_linux_$(dpkg --print-architecture)"
chmod +x "$HOME/.local/bin/yq"
yq --version
Databases for tests
Starts PostgreSQL and Redis and hands their connection URLs to the agent. Installing PostgreSQL takes a minute or two — set the timeout to 10 minutes.
#!/usr/bin/env bash
# Databases for tests: PostgreSQL and Redis running in the sandbox.
# Installing PostgreSQL takes a minute or two; allow a timeout of 10 minutes.
set -euo pipefail
echo "::step::Install PostgreSQL and Redis"
sudo apt-get update -qq
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y -qq --no-install-recommends \
postgresql redis-server
echo "::step::Start PostgreSQL"
sudo service postgresql start
sudo -u postgres psql -qc "CREATE ROLE app LOGIN SUPERUSER PASSWORD 'app';"
sudo -u postgres createdb -O app app_test
echo "::step::Start Redis"
# Services started here keep running after the script ends.
redis-server --daemonize yes --save "" --appendonly no
echo "DATABASE_URL=postgres://app:app@localhost:5432/app_test" >> "$PS_ENV"
echo "REDIS_URL=redis://localhost:6379/0" >> "$PS_ENV"
Private registry
Installs from a private npm registry with a token only the script sees. Add a secret reference named
NPM_TOKEN to the profile, and replace @your-org and the registry URL with yours. The token is
written to a temporary file that is deleted when the script ends.
#!/usr/bin/env bash
# Install from a private npm registry with a token the agent never sees.
# Add a secret reference named NPM_TOKEN to this profile first.
set -euo pipefail
echo "::step::Authenticate to the private registry"
# Keep the token in a temporary config that is deleted when the script ends,
# so it is not left on disk for the agent to read.
NPM_CONFIG_USERCONFIG="$(mktemp)"
export NPM_CONFIG_USERCONFIG
trap 'rm -f "$NPM_CONFIG_USERCONFIG"' EXIT
cat > "$NPM_CONFIG_USERCONFIG" <<NPMRC
@your-org:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${NPM_TOKEN}
NPMRC
echo "::step::Install dependencies"
if [ -f package-lock.json ]; then
npm ci
elif [ -f package.json ]; then
npm install
fi
Go and Rust
Downloads Go modules and Rust crates ahead of time and puts an installed Go tool on the agent's PATH.
#!/usr/bin/env bash
# Go and Rust projects: warm the module caches and add developer tools.
# Go and a stable Rust toolchain are preinstalled.
set -euo pipefail
if [ -f go.mod ]; then
echo "::step::Download Go modules"
go mod download
echo "::step::Install Go tools"
go install golang.org/x/tools/cmd/goimports@latest
echo "$(go env GOPATH)/bin" >> "$PS_PATH"
fi
if [ -f Cargo.toml ]; then
echo "::step::Fetch Rust crates"
cargo fetch
fi