Skip to content

Lifecycle Scripts

Lifecycle scripts build, configure, and grade your sandbox. There are four: build.sh, setup.sh, answer.sh, and score.sh.

All scripts must begin with #!/usr/bin/env bashp — not #!/bin/bash. The bashp preprocessor inlines library functions like install::apt_packages and scoring::check before execution. It also implies set -ex, so only options that differ from that default need to be set explicitly (e.g. set -uo pipefail or set +e).

When each script runs

Script When What to put here
build.sh Once, at image build time Package installs, tool downloads, static files, service enables
setup.sh Fresh-session setup; see local command behavior below Readiness waits only — leave blank when possible
answer.sh During testing only The steps a perfect candidate would take
score.sh When the candidate submits Pass/fail checks followed by scoring::report

build.sh

Runs once when the VM image is built. Results are baked into the image, so sandboxes start fast.

#!/usr/bin/env bashp

install::apt_packages vim git nginx
systemctl enable --now nginx

Enable and start services here, not in setup.sh

Use systemctl enable --now <service> in build.sh. The --now flag both enables the service for boot and starts it immediately during the build, so subsequent build steps can use it, such as pulling a container image. Enablement is baked into the image; the service starts again when a new VM boots.

Sandbox network access

Sandbox VMs have full outbound internet access. Prefer installing packages in build.sh over downloading them at runtime — not because the network is unavailable, but because runtime fetches from external URLs are brittle: upstream packages move, URLs change, and rate limits break otherwise-passing items.

setup.sh

The platform runs setup.sh during fresh candidate or learner session setup. Locally, sandbox setup, sandbox answer, sandbox score, and sandbox test run it as part of their lifecycle sequence. Leave it blank when possible.

Plain sandbox shell, sandbox start, and sandbox attach do not run setup.sh. Background VMs keep their state across attachments and source reloads; updating the mounted script does not reset the VM or apply the script's effects.

Fresh sessions start from the built image. Services enabled in build.sh start at boot, so setup should not repeat package installation or service enablement. See the CLI command and ownership rules before switching between background development and a fresh lifecycle test.

The only work that belongs in setup.sh is waiting for things with a significant, observable startup delay that outlasts SSH availability. Kubernetes cluster readiness is the canonical case (k8s::wait_for_all). Fast-starting services like Docker do not qualify — even if a theoretical race with SSH exists, it has never caused failures in practice.

#!/usr/bin/env bashp

k8s::wait_for_all

For non-Kubernetes sandboxes, setup.sh is typically a no-op:

#!/usr/bin/env bashp

answer.sh

Applies the reference solution. Never runs in production. Locally, sandbox answer, sandbox score, and sandbox test run it after setup. Write it as the exact steps a correct candidate would take.

#!/usr/bin/env bashp

echo "bar" > /tmp/foo

score.sh

Checks the candidate's work. Each check is a function that returns 0 for pass or non-zero for fail. Run checks with scoring::check and end the script with scoring::report.

#!/usr/bin/env bashp

fileExists() {
    test -f /tmp/foo
}

fileContent() {
    grep -q "bar" /tmp/foo
}

scoring::check fileExists
scoring::check fileContent
scoring::report

The combined number of scoring::check and scoring::namedcheck registrations must equal total_check_count in metadata.yaml.

Scoring can run repeatedly. sandbox test runs it twice — before and after answer.sh — and production hooks may retry scoring after a transient failure. Keep checks deterministic and idempotent. A source reload does not run scoring or any other lifecycle script automatically.

Bashp helpers and local libraries

The CLI compiles source scripts into ordinary Bash under dist/<vm>/scripts/. A running local VM mounts that VM's dist directory at /sandbox, so it executes /sandbox/scripts/<name>.sh, not the host's Bashp source.

Functions can come from the CLI's bundled library, a vendored package under bashp-packages/libs/, or VM-local libs/ and scripts/libs/ directories. Names map to files: demo::report can be defined in cp/libs/demo/report. Referenced helpers and their transitive dependencies are inlined into the compiled script.

Since v1.25, the preprocessor tracks quotes separately inside nested $(...) and ${...} expressions. Use ordinary Bash quoting rather than adding escaping workarounds.

Verify library-only reload

Use a VM named cp with an image already built, then start background live reload with CLI v1.27 or later. From the sandbox directory, create these three source files:

bashp-packages/libs/vendor/message
function vendor::message() {
    printf '%s\n' 'vendor-v1'
}
cp/libs/demo/report
function demo::report() {
    printf 'local-v1|%s|%s\n' "$(vendor::message)" "$(system::get_os)"
}
cp/scripts/probe.sh
#!/usr/bin/env bashp

demo::report

The local function uses both the vendored helper and bundled system::get_os. After saving, execute the regenerated script explicitly:

sandbox exec --target cp -- bash /sandbox/scripts/probe.sh

On a Debian image, the output is:

local-v1|vendor-v1|debian

Change only cp/libs/demo/report from local-v1 to local-v2, or only the vendored helper from vendor-v1 to vendor-v2. Leave probe.sh unchanged. Its compiled copy updates automatically; executing it again shows the new library output without restarting the VM.

The same dependency-only reload applies to setup.sh, answer.sh, and score.sh. VM-local changes affect that VM's generated scripts. Shared vendored library changes trigger regeneration for every started VM watching that root. The bundled library is embedded in the CLI; upgrading those bundled implementations requires a CLI update.

Edit source libraries and scripts, not their generated dist/ copies. Reload does not execute the new code or replace function definitions already loaded into an attached shell.

Inspect helpers with an agent

CLI v1.26 introduced the read-only Bashp MCP workflow: find helpers, explain the selected implementation, then resolve the saved lifecycle script or unsaved buffer. Supply the real file context so local overrides and transitive dependencies are resolved for the correct VM.

A resolution pass is not a lifecycle, scoring-policy, or runtime test. Keep the independent content checks and full sandbox acceptance test below.

Test the full lifecycle

Build the current image and verify the untouched and answered states:

sandbox build && sandbox test

This passes when scoring returns zero checks before answer.sh runs, and full marks after. If it fails, the output tells you which check did not pass and at which stage.

Exit any owning shell, or stop background VMs with sandbox stop, before this test. sandbox test owns and cleans up its fresh VM session and does not enable live reload.