Exit codes
Every command ends with one of six exit codes. A script or CI step can branch on them without reading the output.
| Code | Meaning | Typical cause |
|---|---|---|
| 0 | Success | Also --help and --version |
| 1 | Validation or test failure | A template has errors or does not match its snapshot, a generated file is out of date, a preview could not be rendered; see the list below. Diagnostics are printed. |
| 2 | Usage error | Unknown command or flag, a missing argument, init in a folder that already has formfeed.json, no formfeed.json for a command that needs a project, a template or data set that does not exist, a formfeed.json or user config that is not valid JSON, a port of formfeed dev that is in use, and API answers in the 4xx range not listed under 3 and 5, such as 404 template_not_found, 409 conflict (templates push after someone saved a newer version, partials push) or a 422 template error |
| 3 | Authentication error | No key, an unknown or revoked key (401), and every 403: a missing scope, a plan that does not include the feature, test_output_unavailable. A browser login that was refused or whose code expired. |
| 4 | Network or server error | Timeout, connection failure, 5xx (503 after the retries), a render or conversion that failed while the CLI waited for it (render, pdf convert), timeout when it did not finish in time, a library file files pull could not download, and any error the CLI does not classify |
| 5 | Quota or rate limit | 402 quota_exceeded, 429 rate_limited after the retries |
The CLI calls the API through the TypeScript SDK, so 429 and 503 are retried up to three times after the server's Retry-After before a code is chosen. An API answer maps to a code by its status: 401 and 403 give 3, 402 and 429 give 5, any other 4xx gives 2, and everything else 4.
Exit code 1 comes from:
validatefinding an error, including a missing partial. A variable that is missing from the data set is a warning and does not fail.testfinding a snapshot difference, a data set with errors, or a Word or PowerPoint template that cannot be filled. A data set without an approved snapshot does not fail.types --checkwhen the file differs from whatformfeed typeswould write.ci previewwhen at least one render failed or a Word or PowerPoint template could not be filled; the summary lists it.renders pull --runor--snapshotwhen the template does not render with the pulled data.renderwhen a Word or PowerPoint template cannot be filled (the findings name the part and paragraph), and when a template whosepdf.uacheck isstrictfails the PDF/UA-1 validation; the CLI prints the rules that failed.einvoicewhen the template's e-invoice settings are invalid or the_invoiceblock of the data set does not make an e-invoice.templates pushwhen a template includes a partial that has no file, andtemplates push,channels set,promoteandrollbackwhen the schema check finds a breaking change and--allow-breakingis missing.- A template folder that holds
template.htmlandtemplate.docx(ortemplate.pptx) at once, atemplate.jsonwhosekinddoes not match the folder's file, a JSON file in the folder that does not parse, and a.formfeed/brand.jsonthat does not parse. import apitemplatewhen not a single template could be imported.
Two cases that look alike end differently. A render the API refuses in its answer (a problem such as 422 template_runtime_error or 422 page_limit_exceeded) exits with 2 in render and stops ci preview with 2 before its summary is written; a render that fails after it was queued exits with 4 in render and is listed as failed by ci preview, which then exits with 1.
Output on failure
Without --json, a failure prints error: <message> to stderr, followed by the problem's field errors when the API sent any.
With --json, a failure prints a single JSON object to stdout with ok: false, error (the message), exit_code and details, which holds the problem from the server for API errors. For formfeed render invoice --remote --json when the workspace has no published template invoice:
{
"ok": false,
"error": "No published template \"invoice\" in workspace production",
"exit_code": 2,
"details": {
"type": "https://docs.formfeed.dev/errors/template-not-found",
"title": "Template not found",
"status": 404,
"detail": "No published template \"invoice\" in workspace production",
"code": "template_not_found",
"request_id": "req_…"
}
}
Commands that print their own result first (validate, test, ci preview, renders pull, import apitemplate) exit with the code without a second object; their result carries ok: false or the failed rows. An unknown command or flag prints the usage message to stderr, never a JSON object.
formfeed render invoice --json || echo "failed with $?"