Exit codes
Rousseau's CLI is deliberately conservative — two exit codes cover every path.
| Code | Emitted by | Meaning |
|---|---|---|
| 0 | cmd/rousseau/main.go via cli.Execute |
Command completed successfully. Daemons exit 0 on graceful shutdown (SIGINT / SIGTERM). |
| 1 | cmd/rousseau/main.go via cli.Execute |
Command failed. The error string is printed to stderr. Every failure — config parse error, provider auth failure, transport panic, tool wiring error — maps to this code. |
rousseau doctor follows the same convention: exit 0 when every check passes, exit 1 when any check is fail. Warnings and info-level rows do not affect the exit code.
Future releases may split failures into distinct codes (config vs runtime vs network). Today, treat any non-zero exit as retryable but requiring log inspection.
Signal handling
cmd/rousseau/main.go installs a signal handler that cancels the root context.Context on SIGINT and SIGTERM. Every long-lived component (agent loop, transport, cron scheduler, MCP server) honours context cancellation, so the shutdown path is:
SIGINT/SIGTERMreceived.- Root context is cancelled.
- Transports call
Stop()on themselves, flushing in-flight messages. - Cron scheduler stops accepting new fires; running fires complete.
- Session store
Close()is called viadefer, checkpointing the WAL. Executereturns 0.
SIGKILL cannot be caught. If the daemon is kill -9'd mid-turn, the session store's WAL protects against corruption but the in-flight turn is not persisted. The next launch resumes from the last saved state.
systemd restart policy
For the reference Quadlet unit:
[Service]
Restart=on-failure
RestartSec=10
on-failure restarts on any non-zero exit; combined with rousseau's exit code convention this means: exit 0 (SIGTERM from systemctl stop) does not restart, exit 1 does.
For daemons that hit persistent errors (bad config, wrong provider auth), on-failure will thrash. Watch journalctl for the failure reason before assuming the retry loop will recover.
Kubernetes probe semantics
Rousseau ships no HTTP liveness/readiness endpoint by design. Kubernetes probes must be either:
execprobes runningrousseau doctor --config /etc/rousseau/config.yaml(returns 0 on healthy, 1 on failure), or- Absent, with the pod relying on
restartPolicy: Alwaysand the daemon's own error handling.
rousseau doctor is cheap (~50ms) so it is a fine liveness probe. Do not use it as a readiness probe — a fail on provider.claudecli.binary shouldn't take the pod out of rotation if the failure will not self-heal.
Handled errors
Errors that produce exit code 1 by way of the CLI error surface include:
- Config load failure — YAML parse error, unknown field, invalid type.
- Provider auth failure — missing API key, invalid credentials, invalid Bedrock / Vertex region.
- Transport startup failure — missing token, unreachable IMAP/SMTP host, whatsmeow protocol error.
- Store open failure — permission denied on
~/.local/share/rousseau/, disk full. - Doctor check failure — any
failrow makes doctor return exit 1. - Cron cron-expression parse failure —
rousseau cron addvalidates before persisting.
Unhandled panics
go test -race is run on every CI build, so panics are extremely rare. When they do happen, the Go runtime prints the panic + stack trace to stderr and exits with a non-zero code from the runtime — typically 2, but this is Go's convention and not something rousseau controls.
For production, wrap the daemon in a supervisor that captures stderr on abnormal exit and reports the trace.
Next
- User Guide: CLI — every command.
- Guides: Observability — surface the slog signal beyond the exit code.
- Troubleshooting — what to do when the exit code is not enough.