Skip to content

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

CodeNameDescriptionCommon Causes
0SuccessCommand completed successfullyBuild passed, tests passed
1FailureBuild, test, or command failure (expected runtime failure)Compilation error, test failure
2Configuration ErrorInvalid configuration, schema violation, or semantic validation errorMalformed JSON, missing field, cycle
3Environment ErrorExternal system unavailable, I/O failure, or missing runtime dependencyDocker unavailable, permission denied

Go Constants

For Go integrations, use the constants from pkg/structyl:

Exit CodePublic API (pkg/structyl)Internal (internal/errors)
0ExitSuccessExitSuccess
1ExitFailureExitRuntimeError
2ExitConfigErrorExitConfigError
3ExitEnvErrorExitEnvError

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 2 is 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 target
  • structyl xyz — unknown command

Exit Code Usage

bash
structyl build cs
echo $?  # 0 on success, 1 on build failure

Scripting with Exit Codes

bash
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
fi

Failure Modes

Single Target Failure

When a single target command fails:

bash
structyl build cs  # Exit code 1 if build fails

The command exits immediately with the target's exit code.

Multi-Target Failure

When running commands across multiple targets:

bash
structyl build     # Builds all targets
structyl test      # Tests all language targets

Structyl 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:

ReasonDescriptionExample
disabledCommand explicitly set to null in configuration"pack": null
command_not_foundExecutable not found in PATHcargo not installed
script_not_foundnpm/pnpm/yarn/bun script missing from package.jsonnpm 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

ReasonMessage 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 completed

In 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 found

Target-specific failure:

[cs] build: failed with exit code 1

Warning message:

warning: unknown field "foo" in targets.cs

Verbosity Levels

LevelFlagOutput
Quiet-q, --quietErrors only
Normal(default)Errors + summary
Verbose-v, --verboseFull output from all targets

Command Exit Codes

test-summary Exit Codes

The test-summary command parses go test -json output and returns:

Exit CodeCondition
0All tests passed
1File 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 CodeStructyl Exit Code
00 (success)
1-2551 (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: required

Exit 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.

ConditionError MessageExit Code
Unknown toolchain nametarget "{name}": unknown toolchain "{toolchain}"2
Toolchain extends unknown basetoolchain "{name}": extends unknown toolchain "{base}"2

Unknown toolchains are detected even if no command from that toolchain is ever invoked:

json
{
  "targets": {
    "rs": {
      "toolchain": "carg" // typo → detected at load time
    }
  }
}
structyl: target "rs": unknown toolchain "carg"

Flag Validation

Invalid flag values cause immediate errors:

ConditionError MessageExit Code
Invalid --type valueinvalid --type value: "{value}" (must be "language" or "auxiliary")2

Version Command Errors

ConditionError MessageExit Code
bump prerelease on release versioncannot 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 null in 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 available

Exit 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.

bash
structyl build 2>structyl.log  # Structyl logs to file
structyl build >build.log      # Target output to file

Timestamps

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:

bash
NO_COLOR=1 structyl build

Troubleshooting

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=1 and higher values

Solutions:

  1. Set STRUCTYL_PARALLEL=1 for targets with strict ordering requirements
  2. Ensure dependencies are declared correctly in depends_on
  3. 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).

ValueBehavior
Unset or emptyDefaults to runtime.NumCPU() (minimum 1)
Positive integer 1-256Run up to N targets concurrently
Invalid valueWarning 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 default

Validation 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:

  1. Install the missing tool (e.g., cargo, go, npm)
  2. Ensure the tool is in your PATH
  3. If using mise, run mise install to install configured tools
  4. 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 found or similar shell error
  • Commands fail immediately without executing any targets

Solutions:

  1. Install mise following the mise installation guide
  2. Ensure mise is in your shell's PATH
  3. Restart your shell or run source ~/.bashrc / source ~/.zshrc
  4. 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:

  • mise commands fail with parse errors
  • Error messages referencing mise.toml syntax

Solutions:

  1. Delete mise.toml and let Structyl regenerate it: rm mise.toml && structyl targets
  2. Run structyl mise sync to regenerate (always overwrites)
  3. If custom tasks are needed, set auto_generate: false and maintain mise.toml manually
  4. Check .structyl/config.json for invalid target or command definitions

Recovery Strategies

Clean Build

If builds fail mysteriously, try a clean build:

bash
structyl clean
structyl build

Docker Reset

If Docker builds fail:

bash
structyl docker-clean
structyl build --docker

Configuration Check

Validate configuration without running commands:

bash
structyl config validate

See Also

© 2026 Andrey Akinshin MIT