Every sunscreen error has three things: a numeric exit code, a stable string code, and an actionable next_step.
| Exit | Code | Meaning |
|---|---|---|
0 |
— | success |
1 |
unexpected |
a bug — please report with RUST_BACKTRACE=1 output |
2 |
toolchain_missing |
a required external tool (anchor, solana, …) was not found |
3 |
invalid_config |
sunscreen.yml failed schema validation |
4 |
user_input |
conflicting / ambiguous / unrecognized user request |
5 |
missing_workspace |
no sunscreen.yml found upward from cwd |
9 |
plugin_runtime |
a plugin crashed, violated the sandbox, or returned a protocol error |
These are stable identifiers you can match on in scripts. Subset, full list lives in src/error.rs.
code |
next_step |
|---|---|
toolchain_missing.anchor |
Install via AVM: cargo install --git https://github.com/coral-xyz/anchor avm --locked && avm install latest |
toolchain_missing.solana |
sh -c "$(curl -sSfL https://release.solana.com/stable/install)" |
toolchain_missing.pnpm |
npm i -g pnpm |
toolchain_missing.cargo_build_sbf |
Comes with the solana install — re-run the solana installer |
code |
Trigger |
|---|---|
invalid_config.schema |
Top-level shape doesn't match v1 |
invalid_config.field.<path> |
A specific field is wrong (path follows the YAML, e.g. invalid_config.field.plugins.0.version) |
invalid_config.version_unknown |
YAML declares a version newer than this binary supports |
code |
Trigger |
|---|---|
user_input.path_conflict |
Target directory exists (sunscreen refuses to overwrite without --force) |
user_input.symbol_exists |
A scaffold target (instruction, account, …) is already defined |
user_input.recipe_preflight |
A recipe's preflight detected conflicts |
user_input.ambiguous_flag |
Two flags conflict (e.g. --dry-run + --force) |
user_input.semver |
--version argument isn't valid semver |
network.rate_limited |
Faucet returned 429 |
network.unreachable |
RPC didn't respond |
code |
Trigger |
|---|---|
missing_workspace |
No sunscreen.yml in cwd or ancestors |
code |
Trigger |
|---|---|
plugin_runtime.protocol |
Plugin emitted invalid JSON-RPC |
plugin_runtime.sandbox |
Plugin tried to write outside workspace or open a forbidden network connection |
plugin_runtime.crash |
Plugin exited unexpectedly (non-zero, no response) |
plugin_runtime.manifest |
Plugin manifest invalid or unreadable |
Every error includes a next_step field — a short, imperative sentence telling you what to do. This is part of the stable surface (tested by tests/errors_contract.rs).
Human-readable form:
error: user_input.symbol_exists: instruction "create_post" already exists (exit 4)
next_step: Pass --force to overwrite, or pick a different name.
JSON form:
{
"status": "error",
"error": {
"code": "user_input.symbol_exists",
"message": "instruction \"create_post\" already exists",
"next_step": "Pass --force to overwrite, or pick a different name"
}
}Exit 1 (unexpected) is a bug. Include in the issue:
sunscreen --version- The full command you ran
- Output with
RUST_BACKTRACE=1 sunscreen doctor --json