Error Handling
Terminology: This specification uses RFC 2119 keywords (MUST, SHOULD, MAY, etc.) to indicate requirement levels.
This document defines error handling semantics for Structyl.
Exit Codes
| Code | Name | Description | Common Causes |
|---|---|---|---|
0 | Success | Command completed successfully | Build passed, tests passed |
1 | Failure | Build, test, or command failure (expected runtime failure) | Compilation error, test failure |
2 | Configuration Error | Invalid configuration, schema violation, or semantic validation error | Malformed JSON, missing field, cycle |
3 | Environment Error | External system unavailable, I/O failure, or missing runtime dependency | Docker unavailable, permission denied |
Go Constants
For Go integrations, use the constants from pkg/structyl:
| Exit Code | Public API (pkg/structyl) | Internal (internal/errors) |
|---|---|---|
| 0 | ExitSuccess | ExitSuccess |
| 1 | ExitFailure | ExitRuntimeError |
| 2 | ExitConfigError | ExitConfigError |
| 3 | ExitEnvError | ExitEnvError |
External tools SHOULD use the pkg/structyl constants. Internal constants alias public constants, with ExitRuntimeError providing semantic clarity for the generic ExitFailure.
Naming convention rationale:
ExitFailure(public) is generic, suitable for scripting: "did it fail?"ExitRuntimeError(internal) is specific, suitable for debugging: "what category of failure?"
Exit Code Categories
Code 1 (Failure) indicates the user's project has an issue that Structyl correctly detected. The configuration is valid; the build/test simply failed.
Code 2 (Configuration Error) indicates the Structyl configuration itself is invalid or contains semantic errors. The user MUST fix .structyl/config.json or related configuration before proceeding.
Code 3 (Environment Error) indicates an external system or resource is unavailable. The configuration may be valid, but the environment cannot support the requested operation.
Not Found Errors
When a target, command, or resource is not found at runtime, Structyl returns exit code 1 (Failure), not exit code 2. This distinction is intentional:
- Exit code
2is reserved for configuration file errors (syntax, schema violations) - "Not found" during command execution is a runtime failure (the command ran but the target/resource doesn't exist)
Examples that return exit code 1:
structyl build nonexistent— unknown targetstructyl xyz— unknown command
Exit Code Usage
structyl build cs
echo $? # 0 on success, 1 on build failureScripting with Exit Codes
if structyl test; then
echo "All tests passed"
else
case $? in
1) echo "Tests failed" ;;
2) echo "Configuration error" ;;
3) echo "Missing dependency" ;;
*) echo "Unknown error" ;;
esac
fiFailure Modes
Single Target Failure
When a single target command fails:
structyl build cs # Exit code 1 if build failsThe command exits immediately with the target's exit code.
Multi-Target Failure
When running commands across multiple targets:
structyl build # Builds all targets
structyl test # Tests all language targetsStructyl uses fail-fast behavior:
- Stop on first failure
- Exit with code
1 - Report which target failed
Note: There is no continue-on-error mode. Structyl delegates to mise for task execution, and mise stops on first failure.
--continue Flag Removed
The --continue flag has been removed. Using it results in an error. For continue-on-error workflows, use continue_on_error: true in CI pipeline step definitions (see CI Integration).
Skip Errors
A skip error indicates a command was skipped (not failed). Skip scenarios include:
| Reason | Description | Example |
|---|---|---|
disabled | Command explicitly set to null in configuration | "pack": null |
command_not_found | Executable not found in PATH | cargo not installed |
script_not_found | npm/pnpm/yarn/bun script missing from package.json | npm run test with no test script |
Skip Error Behavior
- Skip errors are logged as warnings, not failures
- Execution continues after a skip
- Skip errors do NOT affect exit code (exit 0 unless actual failure occurs)
- Skip errors are excluded from combined error results
Skip Error Message Formats
| Reason | Message Format |
|---|---|
disabled | [{target}] {cmd}: disabled, skipping |
command_not_found | [{target}] {cmd}: {executable} not found, skipping |
script_not_found | [{target}] {cmd}: script '{script}' not found in package.json, skipping |
Where:
{target}— target name (e.g.,go,ts,cs){cmd}— command name (e.g.,build,test){executable}— the executable that was not found (e.g.,go,cargo){script}— the npm/pnpm/yarn/bun script name (e.g.,test,build)
Example Output
warning: [go] build: go not found, skipping
warning: [ts] test: script 'test' not found in package.json, skipping
[cs] build completedIn this example, the overall command succeeds (exit 0) because cs built successfully, even though go and ts were skipped.
Error Messages
Format
Structyl produces two types of error messages:
CLI-level errors (configuration, usage, environment):
structyl: <message>Target-specific failures (build, test failures):
[<target>] <command>: <message>Format Grammar
cli_error := "structyl: " message LF
target_error := "[" target "] " command ": " message LF
warning := "warning: " message LF
target := [a-z][a-z0-9-]*
command := [a-z]+
message := <single line, no newline>
LF := "\n"Notes:
- Target names are always lowercase (matching target slug)
- Messages are single-line
- Each error line ends with LF (Unix newlines, even on Windows)
Examples
CLI-level error:
structyl: configuration file not foundTarget-specific failure:
[cs] build: failed with exit code 1Warning message:
warning: unknown field "foo" in targets.csVerbosity Levels
| Level | Flag | Output |
|---|---|---|
| Quiet | -q, --quiet | Errors only |
| Normal | (default) | Errors + summary |
| Verbose | -v, --verbose | Full output from all targets |
Command Exit Codes
test-summary Exit Codes
The test-summary command parses go test -json output and returns:
| Exit Code | Condition |
|---|---|
| 0 | All tests passed |
| 1 | File not found, no valid test results parsed, or any failed |
Target Command Normalization
Commands executed by Structyl SHOULD use standard exit codes. Structyl normalizes exit codes as follows:
| Target Exit Code | Structyl Exit Code |
|---|---|
| 0 | 0 (success) |
| 1-255 | 1 (failure) |
The original target exit code is logged for debugging but not propagated directly to the caller.
Configuration Validation
On startup, Structyl validates .structyl/config.json:
structyl: failed to load configuration: project.name: requiredExit code: 2
Error Aggregation
When .structyl/config.json contains multiple validation errors, Structyl reports errors one at a time. Users MUST fix the reported error and re-run to see subsequent validation errors.
Rationale: This fail-fast approach simplifies implementation and encourages incremental fixes. Future versions MAY aggregate validation errors.
Toolchain Validation
Toolchain references are validated at configuration load time, not at command execution time. This ensures early detection of configuration errors.
| Condition | Error Message | Exit Code |
|---|---|---|
| Unknown toolchain name | target "{name}": unknown toolchain "{toolchain}" | 2 |
| Toolchain extends unknown base | toolchain "{name}": extends unknown toolchain "{base}" | 2 |
Unknown toolchains are detected even if no command from that toolchain is ever invoked:
{
"targets": {
"rs": {
"toolchain": "carg" // typo → detected at load time
}
}
}structyl: target "rs": unknown toolchain "carg"Flag Validation
Invalid flag values cause immediate errors:
| Condition | Error Message | Exit Code |
|---|---|---|
Invalid --type value | invalid --type value: "{value}" (must be "language" or "auxiliary") | 2 |
Version Command Errors
| Condition | Error Message | Exit Code |
|---|---|---|
bump prerelease on release version | cannot bump prerelease on release version "{version}" | 2 |
A release version is one without a prerelease identifier (e.g., 1.2.3 is a release version; 1.2.3-alpha is not). The bump prerelease operation requires an existing prerelease suffix to increment.
Dependency Checks
Before running commands, Structyl checks dependencies:
Missing Command Definition
When a command is invoked on a target that doesn't define it:
[cs] build: command "build" not defined for target "cs"Exit code: 1 (Failure)
This is a runtime failure, not a configuration error, because the target exists and the configuration is valid—the target simply doesn't support the requested command. Compare with toolchain validation errors (exit code 2), which are detected at configuration load time.
Note: This differs from null commands. A command explicitly set to
nullin configuration produces a skip error (exit 0 with warning), not a failure. An undefined command (neither in target config nor inherited from toolchain) is an error (exit 1).
Missing Docker
structyl: Docker is not availableExit code: 3
Partial Failure Summary
For multi-target operations, Structyl prints a summary at the point of failure (due to fail-fast behavior):
════════════════════════════════════════
Summary: test
════════════════════════════════════════
Total time: 12s
Succeeded: 3 (cs, go, kt)
Failed: 1 (py)
Skipped: 0
Not started: 3 (r, rs, ts)
Failed targets:
py: Test failed: test_center.py:42
════════════════════════════════════════Note: Due to fail-fast behavior, exactly one target can fail before execution stops. Any targets that were not yet started when the failure occurred are listed as "Not started".
Logging
Log Output
Structyl logs to stderr. Target output goes to stdout.
structyl build 2>structyl.log # Structyl logs to file
structyl build >build.log # Target output to fileTimestamps
Each log line includes a timestamp:
[14:32:05] Building cs...
[14:32:08] cs: build completed
[14:32:08] Building py...Colors
Colors are enabled by default for terminal output. Disable with the NO_COLOR environment variable:
NO_COLOR=1 structyl buildTroubleshooting
Parallel Execution Race Conditions
When STRUCTYL_PARALLEL > 1, dependency ordering is NOT guaranteed. If a target depends on another via depends_on, the dependent may start before its dependency completes.
Symptoms:
- Intermittent build failures that pass on retry
- "File not found" errors for generated artifacts
- Different results between
STRUCTYL_PARALLEL=1and higher values
Solutions:
- Set
STRUCTYL_PARALLEL=1for targets with strict ordering requirements - Ensure dependencies are declared correctly in
depends_on - Use explicit synchronization in build scripts if needed
STRUCTYL_PARALLEL Validation
The STRUCTYL_PARALLEL environment variable controls concurrent target execution when using Structyl's internal runner (not mise).
| Value | Behavior |
|---|---|
| Unset or empty | Defaults to runtime.NumCPU() (minimum 1) |
| Positive integer 1-256 | Run up to N targets concurrently |
| Invalid value | Warning logged, falls back to default |
Invalid values that trigger warnings:
- Non-integer:
"invalid","4.0"," 4"(leading/trailing whitespace) - Out of range:
"0","-1","257"(values outside 1-256) - Overflow: values exceeding int64 max
Warning messages (wording is unstable per stability.md):
warning: invalid STRUCTYL_PARALLEL value "<value>" (not a number), using default
warning: STRUCTYL_PARALLEL=<n> out of range [1-256], using defaultValidation warnings do NOT cause non-zero exit codes—the command proceeds with the default worker count.
Command Not Found Skip Errors
When a toolchain command is not found, Structyl skips the target with a warning rather than failing.
Symptoms:
warning: [<target>] <command>: <tool> not found, skipping- Target appears to succeed but no work is done
Solutions:
- Install the missing tool (e.g.,
cargo,go,npm) - Ensure the tool is in your PATH
- If using mise, run
mise installto install configured tools - Check that the toolchain is correctly configured in
.structyl/config.json
Missing mise Installation
When mise is not installed or not in PATH:
Symptoms:
structyl: mise not foundor similar shell error- Commands fail immediately without executing any targets
Solutions:
- Install mise following the mise installation guide
- Ensure mise is in your shell's PATH
- Restart your shell or run
source ~/.bashrc/source ~/.zshrc - Verify installation with
mise --version
Corrupted mise.toml
When mise.auto_generate: true is enabled, Structyl regenerates mise.toml on certain commands. If the generated file is invalid:
Symptoms:
misecommands fail with parse errors- Error messages referencing
mise.tomlsyntax
Solutions:
- Delete
mise.tomland let Structyl regenerate it:rm mise.toml && structyl targets - Run
structyl mise syncto regenerate (always overwrites) - If custom tasks are needed, set
auto_generate: falseand maintainmise.tomlmanually - Check
.structyl/config.jsonfor invalid target or command definitions
Recovery Strategies
Clean Build
If builds fail mysteriously, try a clean build:
structyl clean
structyl buildDocker Reset
If Docker builds fail:
structyl docker-clean
structyl build --dockerConfiguration Check
Validate configuration without running commands:
structyl config validateSee Also
- Error Codes Reference - Quick reference for error codes and recovery
- Stability Specification - Error message format stability guarantees