This page maps Gula's internal failure modes to the process exit code of
the gula binary. Supervisor commands mostly use 0/1; local operator
clients such as status, start, stop, and restart also reserve specific
codes for supervisor availability and protocol failures. The table below
explains which conditions land in which bucket.
| Exit code | Meaning |
|---|---|
0 |
All managed processes finished successfully, a graceful shutdown signal (SIGTERM/SIGINT/SIGHUP/SIGQUIT) stopped otherwise-healthy process groups inside the cleanup window, a read-only operator query rendered successfully, or a requested process-control operation succeeded/was accepted with --no-wait. |
1 |
One or more managed processes failed (non-zero exit, timeout, readiness timeout, policy stop/kill, or restart budget exhausted), the supervisor itself failed before it could start (config load error, validation error, I/O error), the supervisor returned an error envelope, or a process-control operation reached failed. |
2 |
Local operator client could not locate or connect to a running supervisor. Used by gula status and gula start / stop / restart. |
3 |
Local operator client reached a supervisor with an incompatible control-plane API version. |
For gula run, the 0/1 split is driven by the supervisor outcome:
Ok(RunOutcome::AllProcessesSucceeded)→ExitCode::SUCCESS(0).Ok(RunOutcome::OneOrMoreProcessesFailed)→ExitCode::FAILURE(1). At least one process reported a failure outcome but Gula itself ran to completion.Err(_)→ propagated bymain, also1.
RunOutcome::OneOrMoreProcessesFailed is emitted when supervision ran
end-to-end but at least one process did not finish cleanly. Concretely, any of
the following per-process outcomes promotes the whole run to
OneOrMoreProcessesFailed:
| Per-process outcome | Internal reporting path |
|---|---|
Spawn syscall failed (e.g. binary missing, EACCES, scheduling rejection) |
SpawnError { name, hint, source } |
timeout_seconds exceeded |
Timeout(name, seconds) |
readiness_probe.timeout_seconds exceeded |
ReadinessTimeout(name, seconds) |
| Process exited non-zero with no retry left | ProcessStats.exit_code != 0 when no restart_policy is configured; RestartRequested(name) after the final restart attempt |
memory_threshold_action: restart or iceoryx.on_dead: restart fired and exhausted the restart budget |
RestartRequested(name) |
memory_threshold_action: kill/stop or iceoryx.on_dead: kill/stop fired |
PolicyViolation { process_name, policy_name, action } |
Per-process I/O error (log-file creation, metrics CSV write, /proc read) |
Io(_) |
A SKIPPED process (its dependency failed) also contributes to
OneOrMoreProcessesFailed: the run is not considered "all green" if any
process never got to start. An operator stop does not contribute to
OneOrMoreProcessesFailed by itself; the process supervisor remains alive for
a later start/restart until the whole supervisor is shut down.
Err(_) is reserved for failures that prevent Gula from supervising at all.
These come from the supervisor Error enum used by top-level dispatch:
| Failure | Error variant |
|---|---|
| YAML failed to parse (unknown field, bad type) | surfaces as Error::Other(anyhow) from serde_yaml |
GulaConfig::validate() rejected the config (duplicate names, cycles, invalid affinity, out-of-range timer, ...) |
Error::Config { .. } |
GulaSystemConfig::validate() rejected the system config |
Error::Config { .. } |
Per-process error escalated to a hard error during setup (e.g. cannot create logs_dir) |
Error::Process(ProcessError::Io) or Error::Io |
Generic I/O error during setup (signal handler install, /proc access, ...) |
Error::Io(_) |
| Anything else | Error::Other(anyhow::Error) |
Both Err(_) and RunOutcome::OneOrMoreProcessesFailed produce CLI exit code
1. The difference is operational:
Err(_)⇒ Gula never supervised anything; check the message printed to stderr.RunOutcome::OneOrMoreProcessesFailed⇒ Gula supervised normally; per-process logs inlogs_dir/contain the per-process failure detail, andgula_metrics.csvwill have any pre-failure samples.
Receiving SIGTERM, SIGINT, SIGHUP, or SIGQUIT is treated as a
deliberate shutdown. Per-process exits caused by the forwarded signal are
classified as a clean shutdown, not as a failure, so a normal Ctrl-C does not
turn otherwise healthy services into exit code 1.
The shutdown signal does not erase failures Gula already observed before the
signal arrived. If one process exits non-zero, times out, fails readiness, or
hits a terminal policy action, and a later Ctrl-C or SIGTERM stops the
remaining processes, the run still returns
RunOutcome::OneOrMoreProcessesFailed and the CLI exits 1. Likewise, a
group that had already entered leader-failure cleanup before the shutdown
signal remains a failed process result.
gula validate --config gula.yaml --sys-config gula_config.yaml || exit 1
if ! gula run --config gula.yaml --sys-config gula_config.yaml; then
echo "supervised stack failed; see logs/"
exit 1
fiFor programmatic integration, call the gula binary and branch on its exit
code. The 0.1.x line does not expose a supported Rust embedding API.