Flitzdocs
CLI

Exit codes and messages

The six-code exit contract for script authors, the message convention, verbose output, and the bug-report capture.

Every flitz failure branches on the same small set of exit codes. This page is the one place a script author needs.

Exit codes

flitz reports exactly six codes, so a script can branch on the class of failure without matching prose:

CodeClassMeaning
0SuccessThe command did what it was asked.
1Usage or configurationThe invocation, or the project/machine/organization state it names, is wrong and the fix is yours: an unknown command or flag, a missing or malformed argument, --json on a command that does not support it, a required confirmation that could not be obtained non-interactively, a missing or malformed pin or flitz.yaml, a release with no SDK for this machine, an unsatisfiable plugin resolution, or a refusal that is yours to fix: an archived, deleted, or unknown app, an app key another app already holds, an unknown publish id, or a new app you did not confirm.
2General or unexpectedThe catch-all: a local filesystem or permission failure, an unresolvable home directory, a lock that could not be acquired, an interrupted command, or an unexpected error.
3Not authorizedNo valid credential, a rejected or expired one, an inactive organization license, or an interactive-only operation attempted under an API key.
4Service or networkThe service or a link was unreachable, timed out, returned a persistent error or a response the CLI could not read; an upload failed; or a write-once destination was already occupied.
5BuildThe app failed to compile: the kernel compilation failed or bytecode compilation failed step.

A build failure is code 5 whichever of the two compile steps produced it; the message names the step. See Troubleshooting for what each one means.

A flitz.yaml that is not valid YAML, is not a mapping, or gives a key a non-string value is a usage error (exit 1) naming the file. Fix it, or delete it and re-run.

One subtlety around publish: code 2 is the only code that can follow a publish whose bundle upload succeeded. Once the URLs are printed, a later local failure — a --qr-output PNG that could not be written — reports it and exits 2 without retracting the result.

Reading the code in a script

if flitz status --json > status.json; then
  jq -r '.checks[] | select(.verdict=="blocking") | .message' status.json
fi

flitz publish --json > result.json
case $? in
  0) deploy "$(jq -r .bundle_url result.json)" ;;
  3) echo "not authorized — check FLITZ_APIKEY" >&2 ;;
  5) echo "build failed — see the log above" >&2 ;;
  *) echo "publish failed (exit $?)" >&2 ;;
esac

A --json command writes no JSON on failure. Check the exit status first, then parse stdout only on 0. The one exception is a publish --qr-output failure: it exits 2 with the complete document already on stdout. flitz status always exits 0 — its verdicts live in the check markers, not the exit status.

Message convention

Most errors and warnings are written to stderr in one shape: the verdict marker and a cause, then one indented → line per next action.

✗ <cause, a lowercase sentence naming the concrete value involved>
  → <the next command to run>

In plain output the marker is its ASCII form:

[fail] <cause>
  -> <next command>

What you can rely on:

  • The cause names the concrete value that matters — the tag, the path, the host, the HTTP status.
  • A hint names the next thing to run. A cause with no useful next step carries none.
  • Compiler output passes through verbatim. When a compile step fails, its raw output is printed unreformatted beneath a framing line naming the tool; the CLI does not parse, re-wrap, or summarize it.
  • Secrets are redacted in error causes, in the bug-report capture, and in tool output quoted on failure: credentials, the API key, plugin tokens, and signed URLs. A signed URL keeps its origin and path with its query replaced by ?<redacted>, and an echoed build command keeps each --dart-define key with its value replaced by <redacted>.

Some messages use a different shape. Argument errors print the message followed by the command's usage text, a few failures are a single plain line without a marker, and a cause can quote a lower-level error verbatim.

Warnings use the ⚠ marker in the same shape and never change the exit code.

Verbose output (-v)

-v (or --verbose) has exactly one level and four effects:

  1. Stack traces on error — every error additionally prints its stack trace beneath the cause.
  2. Live build output — the compile steps stream their output to stderr as it arrives, instead of being captured and only quoted on failure. A long compile stops looking like a frozen spinner.
  3. Fuller flitz status — rows that status otherwise hides are shown; see flitz status.
  4. Retry announcements — a retried network request says so.

-v does not log HTTP requests, does not dump environment variables, and does not enable a log file. A second -v (-vv) is a usage error — there is only one level.

Verbose output is not redacted

What -v streams live, and the stack traces it adds, are printed as they are. Read them before you paste them anywhere.

Without -v, build-tool output is captured: discarded on success, and printed above the error on failure — its last 64 KiB, with --dart-define values redacted. Redaction also replaces long, token-like runs of letters and digits, so a very long identifier in quoted compiler output can show as <redacted>; re-run with -v to see it unmodified.

The bug-report capture

When the CLI hits something it does not recognize — and only then — it prints

✗ unexpected error — please report this
  <exception type>: <message>

followed by a delimited capture block, and exits 2. A recognized, cleanly handled error never emits one; the block exists precisely because the CLI does not know what went wrong.

The block is built from an allowlist — it contains exactly these facts and nothing swept from your environment or filesystem:

FieldContents
CLIVersion and host (for example linux-x64).
OSOperating system and version.
ProjectThe pinned tag (or that none is pinned), whether it is installed, and how many SDKs are cached.
InvocationThe command line. Subcommands, positional arguments (tags, shell names, app keys, publish ids), and flag names are kept; flag values are replaced by <redacted>, except the values of --dir, --target, --qr-output, --dart-define-from-file, and --schema.
AuthBooleans only: whether an interactive sign-in is stored and whether FLITZ_APIKEY is set. The license shows as unknown. Never a token, never an identity.
ErrorThe exception type, its message, and the stack trace.

It never contains the contents of credentials.json, config.yaml, ~/.netrc, or gradle.properties; never a request or response header; never a signed URL. A final redaction pass runs over the whole block as a backstop.

The block is written to stderr only, is never saved to a file, and is never transmitted anywhere. It is built to be safe to paste into a bug report; glance over it before you share it. To report it, contact support and send the block with what you ran.

Interrupting a command

Ctrl-C is handled. The CLI writes one line naming the phase it interrupted, terminates any build tool it started rather than orphaning it, deletes its scratch directory, releases any lock it holds, and exits 2. A resumable SDK download is deliberately kept, so re-running continues rather than restarting. An interrupted publish shows in the publish history as pending, and as abandoned an hour after it started, when only flitz publishes --all lists it. Re-running publish starts a fresh one. A second Ctrl-C leaves immediately, still exit 2.

On this page