Run shell commands before every execution — at image build time or per request — to install tools, configure git, clone repos, or prepare any environment state.
A setup script is a bash script that runs inside the sandbox container before your main code executes. There are two independent levels:
Level
Declared in
When it runs
Persists?
Image-level
prebuiltImages[].setupScript in config
Baked into the Docker image; runs on every execution against that image
Yes — part of the image
Request-level
setupScript in ExecutionRequest
Per execution call, on the live container
No — ephemeral by default
When both are present, the image-level script always runs first, then the request-level script.
Declare setupScript inside a prebuiltImages entry to bake recurring setup into a custom Docker image. The script is written to /sandbox/.isol8-setup.sh inside the image and executed automatically before every execution against that image.Use this for setup that is constant across executions: git identity, SSH configuration, tool symlinks, shell aliases, or any one-time environment preparation.
isol8 hashes runtime + packages + setupScript to produce a deps hash stored as a Docker image label. If neither the packages nor the script has changed, isol8 setup skips the build entirely. Changing a single character in the script invalidates the hash and triggers a rebuild.
# Safe to call on every deploy — rebuilds only when content changesisol8 setup
Pass setupScript directly in an ExecutionRequest to run ad-hoc setup on the container before your code. This is useful for tasks that differ per execution: cloning a specific repo, writing config files, setting environment variables from secrets, or installing a package not in the base image.
When both levels are set, the order is deterministic:
1. Image-level setup script (from prebuiltImages[].setupScript, baked into image)2. Request-level setup script (from ExecutionRequest.setupScript, per-call)3. Main code / agent prompt
If any stage exits non-zero, the remaining stages do not run. In streaming mode, StreamEvent objects with phase: "setup" are emitted during stages 1–2, and phase: "code" during stage 3.
For tools needed on every run, always prefer baking them into a custom image via prebuiltImages[].setupScript or installPackages. Request-level installs run on every call and add latency.
When using executeStream, setup-script output is yielded as StreamEvent objects with phase: "setup" before any phase: "code" events appear. If the script exits non-zero, the stream emits a { type: "error", phase: "setup" } event followed by the exit event, and the main code never runs — no further events are yielded.
for await (const event of engine.executeStream({ runtime: "bash", setupScript: "git clone https://$GITHUB_TOKEN@github.com/my-org/repo.git /sandbox/repo", code: "ls /sandbox/repo",})) { if (event.phase === "setup") { // stdout/stderr/error from the setup script if (event.type === "error") { console.error("Setup failed:", event.data); } else if (event.type === "exit" && event.data !== "0") { console.error("Setup exited with code", event.data); // No more events will follow — main code did not run } } else { // phase === "code" — main code output if (event.type === "stdout") process.stdout.write(event.data); if (event.type === "stderr") process.stderr.write(event.data); }}
The phase field is optional on StreamEvent for backward compatibility, but is always set by isol8 — setup events always carry "setup" and main code events always carry "code". Checking event.phase === "setup" is safe.