The CLI uses five codes, defined as constants in cli/src/runtime.ts: EXIT_OK, EXIT_ERROR, EXIT_USAGE, EXIT_CONFLICT and EXIT_ENVIRONMENT. The seven device commands (login, logout, attach, detach, status, watch, and prune) use the same five values: any unknown command or invalid flag exits with 2; login with a credential that already exists, logout with a credential coming from MALETA_DEVICE_TOKEN, attach with a Maleta already bound, and prune with a maleta watch or another prune running, or with an environment credential for another device, all exit with 3 (EXIT_CONFLICT), as does tray when a live tray instance already holds tray.pid; no device command uses 4.
#Code table
| Code | Constant | When it happens |
|---|---|---|
0 | EXIT_OK | init completed; help printed; validate completed; install or sync finished with failures === 0 — skipped or unsupported items still exit with 0. |
1 | EXIT_ERROR | Unreadable or invalid document; any planning, GitHub resolution, limit, collision, staging, or write error; plugin failure; a corrupt state record during prune; or an interrupted command ([error] interrupted). |
2 | EXIT_USAGE | Unknown command, unknown option, invalid flag value, or flag used with the wrong command. |
3 | EXIT_CONFLICT | A refusal that needs a decision: init on a file that already exists (opened with openSync(target, "wx"), resulting in EEXIST); login with a credential already stored or with MALETA_DEVICE_TOKEN set; attach with a Maleta already bound; watch or prune --yes when another command holds watch.lock; prune --yes when the environment credential belongs to another device; or tray when a live tray already holds tray.pid. |
4 | EXIT_ENVIRONMENT | The file's targetOs differs from the host during install or sync; the native tray binary is missing, lacks permission to execute, or cannot run here; or the tray fails to start — it exits before READY, does not become ready within 10 seconds, or its pid file cannot be created or resolved. A tray that becomes ready and then dies within the 400 ms grace window is instead an exit 1 failure. |
#Usage errors exit with 2
- stderr receives the message
[error] <mensagem>followed byRun 'maleta --help' for usage. - Usage errors are:
unknown command '<x>'(a near-typo adds. Did you mean '<y>'?),unknown option '<x>',unexpected argument '<x>',duplicate --file,duplicate --tool,--file requires a path,--tool requires a value,--tool must be all, claude, codex, or agents,--dry-run is only supported by install and syncand--tool is only supported by install and sync. - If
--helpor-happears anywhere in the arguments, help takes precedence over usage errors and exits with0— except for a command name that is a near-typo of a real command (edit distance ≤ 2), which exits2instead.maleta helpis an accepted spelling ofmaleta --helpand prints the same bytes.
maleta install --tool nope --file ./maleta.json
echo "exit=$?"#The 4 versus 1 caveat
The same targetOs mismatch produces different codes depending on how it is used. On the command line, install and sync compare the value before building the plan: they print [error] targetOs '<x>' does not match the local host '<y>'; refusing install (or refusing sync) and the process exits with 4.
When createInstallPlan is called as a library, the corresponding check throws PlanError with the message targetOs '<x>' does not match the local host '<y>' — without the suffix ; refusing <mode>. If the error is not caught, the exception propagates; callers that catch it and map it to the host process's exit code must treat it as 1, not 4.
#What still exits with 0
- Skills skipped because they do not exist in the local assets: the
[skip] …line is printed, but the summary still reports success. - Plugins skipped because they are Claude-only under another
--tool, or because they are not in the local plugin manifest. maleta validate --tool claude: the flag is accepted and silently ignored, without affecting the result.