CLI Troubleshooting and Exit Codes

Use this page by searching for the literal string the CLI gave you. Every message below is reproduced exactly as it is printed — including two with typos in them, which are quoted verbatim because that is what you will paste into a search box. The Cause column is where the plain-English explanation lives.

Most failures set the process exit code rather than exiting immediately, so the work that runs after a command — including the once-a-day update check — still happens. And one rule governs everything else here: JSON mode does not change the exit code. The document’s ok field and the process exit code are two separate signals, and they do not always agree.

Exit codes

Codeok in the JSON documentMeaningEmitted byWhat to do
0trueSuccess. Also “No changes have been committed. Nothing to push.”, a completed --dry-run, and an empty commit set on notify-cmsevery commandNothing. Note the second column of meanings if you are scripting: a no-op is a success
1falseUnclassified — an unexpected fault in the CLI itself (internal_error)every commandRead the error, and report it with the whole document
2falseUsage — the call itself is wrong: an unknown or invalid flag, or a page, page type or command that does not exist (invalid_arguments, not_found)every command, plus the argument parserFix the command line. leed schema <command> --json gives the real flags
3falsePrecondition unmet — validation, restricted files, wrong branch, a failed build, a rebase conflict, a refused git command, a partial upload, not in a site folderevery commandFollow error.hint
4falseTransient — the CMS or the git host could not be reached, or answered with a server error (network_error)every command that talks to the networkRetry with backoff
5falseNeeds a person — not signed in, session expired, permission denied, a confirmation or an interactive terminal requiredevery commandStop and ask. Retrying fails identically
non-zerotrueThe command’s main work succeeded and a step after it failed: leed site upload when the build-result callback was not delivered, leed site push when the CMS was not notified, a settings publish or page write that succeeded in part. The exit is the failed step’s classthose commandsRead warnings. This row is why nothing may treat ok and the exit code as one signal
18no document is writtenA git hook rejected the command. Always husky, never the CLI itself.husky/pre-commit, pre-push, commit-msgUse leed site commit / leed site push instead of raw git
130 / 143true, with a warningA leed site build --wrangler session interrupted with Ctrl+C or SIGTERMleed site build --wranglerNothing; you stopped it
1n/aThe one-line installer’s fatal(), under set -euo pipefailthe installer shell scriptRead the last line it printed; the installer messages are cataloged below

Branching on failure, not on the exit code

This is the rule to take away from the page.

The exit code is 0 on success and names the kind of failure otherwise: 2 fix the call, 3 satisfy a precondition, 4 retry, 5 ask a person, 1 report a bug. A command may exit non-zero while reporting ok: true when a non-fatal step failed after the result was already determined; the detail is in warnings.

The exit class is derived from error.code by one table, so every failure document also carries it as error.exit, and error.retryable says whether repeating the call unchanged can succeed. The older per-command numbers — 8 and 9 from notify-cms, 2 for every CI-shaped refusal — are retired. A script that tested if [ $? -eq 1 ] would have missed most failures then and misreads them now; test the class, or parse the document.

In JSON mode, the reliable shape is:

doc=$(leed site push --json) || true
if [ "$(printf '%s' "$doc" | jq -r .ok)" = "true" ]; then
  printf '%s' "$doc" | jq -r '.warnings[]?'
else
  printf '%s' "$doc" | jq -r '.error.code, .error.message, .error.hint'
fi

Parse stdout, read ok, branch on error.code. The code enum is closed, so a switch over it can be exhaustive — the fifteen codes, their exit classes and what each one means are on the machine-readable output page, along with the envelope those fields live in.

“You cannot ‘commit’ directly”

This is the error most people meet in their first week, and it is not a bug.

	You cannot 'commit' directly, please use:

		leed site commit -m "update details"

The process exits 18, and nothing is committed.

leed site init installs three husky hooks into your repository: pre-commit, pre-push and commit-msg. The first two run git config --get leed.invoked and refuse unless it reads true — a flag only the CLI sets, and only for the duration of its own git call. A git push you run yourself gets the same treatment with leed site push in the message.

The third hook checks the message itself. A commit message that does not begin @leed/site-management=> and carry a JSON payload with userId, version and message is rejected the same way:

	You cannot commit directly to this repo, please use:

		leed site commit -m "reason for update"

The rule exists because a commit that skipped the CLI skips three things the platform depends on: file validation, the build gate, and the metadata the CMS reads to attribute the change and trace a failed build back to a CLI release. The reasoning in full is on validate, commit and push.

Only committing and pushing are intercepted. git status, git log, git diff, git branch, git checkout and git pull --rebase all behave normally, and you are meant to use them.

Setup and authentication problems

Message (verbatim)CommandCauseFix
Site configuration not initialized. Run `leed site init`!!!any leed site commandleed.config.json still has initialized: falseRun leed site init
Site configuration already complete! Try running a build `leed site build`leed site initThe site is already set up in this folderNothing is wrong. Build instead
Could not locate leed.config.json. Run this application from the directory where your repo exists.any leed site commandYou are not in the site folder or in raw-content/; the CLI looks in exactly those two placescd into the site folder. If the file is gone, download a fresh one from Developer Setup
This command must be run from inside a leed-managed site directory.leed auth rotate, leed auth resetBoth are site-scoped — they rotate the tokens of the site you are standing incd into the site folder
Could not determine the target environment. (the message goes on to name leed.config.json and ENV_NAME)leed site push, commit, list, and the leed import commandsNo leed.config.json names an environment and ENV_NAME is unset or invalid. These commands refuse rather than assume productionRun from inside the site folder, or set ENV_NAME to dev, staging or production
Not authenticated for <env>. Run: leed auth login --env <env>any authenticated commandNo session stored for the resolved environment and companyLog in for that environment and company, not another one
No company selected for <env>. (followed by the --company remedy)any authenticated command outside a site folder, leed auth logoutYou hold sessions for several companies, none is the default, and nothing names onePass --company, run from the site folder, or set a default with leed auth default
No company was selected for this login. Run: leed auth login --env <env> --company <companyId>leed auth loginThe approval bound the session to no company — the account belongs to several and none was picked. The session is kept as an unassigned legacy sessionSign in again with --company
The stored session for <env> belongs to company <id>, not <id>.leed site initThe configuration is for a different company from the session you holdleed auth login --env <env> --company <companyId>
The credential file <path> is unreadable.any command that reads credentials~/.config/leed/auth.json is corrupt. It is reported rather than read as “not logged in”, so nothing overwrites itFix or remove the file, then leed auth login
git 2.31 or newer is required to authenticate against the content repository.leed site init, leed site pushThe repository token reaches git through config environment variables that older git ignoresUpgrade git
Not authenticated.leed site initNo credentials at allleed auth login --browser --env <env>
Session expired or invalid.leed site init, leed auth rotateThe stored token is past the refresh grace windowLog in again
Session expired. Run: leed auth login --env <env>leed auth statusThe developer-credentials check got a 401Log in again
No active organization. Download a fresh leed.config.json from the CMS.leed site initThe session carries no active organizationRe-download the config from Developer Setup
You don't have developer access to this organization.leed auth rotate, leed auth resetYour account lacks the developer overrideAsk an administrator to grant it
No developer access for this organizationleed auth statusThe same 403, reported in the status listingSame
(no tokens stored yet — run 'leed auth rotate' or rerun the installer)leed auth statusYou are signed in, but no GitLab tokens have been issued to this machineleed auth rotate
Not logged in to <env>.leed auth logoutNothing was stored for that environmentNothing to do; the command returns successfully

The first two rows are the only messages on this page that the CLI prints with backticks of its own, around the command it is suggesting. They are set in ordinary type here rather than as code so that those backticks survive to the screen — copy the row and the two strings match your terminal character for character:

Site configuration not initialized. Run `leed site init`!!!
Site configuration already complete! Try running a build `leed site build`

“Signed in” and “able to git fetch” are different states, because they rest on different credentials. If leed auth status shows a valid session but git refuses you, the stored raw-content token is the stale one, and leed auth rotate replaces it — the three-credential model is on CLI authentication.

“Your session is no longer valid. Please re-authenticate to continue.” — or, under --json, auth_expired with “The stored session for … is no longer valid.” — means the CMS refused the stored session. A CLI session lasts 90 days from its last use, so that now has three causes: the session went unused for longer than that plus the 24-hour grace; it was ended by leed auth logout, leed auth reset or a revoke from another device; or it is a login from before CLI sessions became 90-day sessions, which the CMS will not renew. Each takes one leed auth login. Running commands in parallel is no longer a cause — they share one renewal under a lock on auth.json, and a token another command has just replaced keeps working for 60 seconds — and a rate-limited session check (HTTP 429) is reported as network_error, exit 4, not as an expired session. Wait a minute and retry it.

Validation, commit and push problems

Message (verbatim)CommandCauseFix
Error: Restricted file violation!leed site validateYou modified a protected path, a read-only file, a disallowed file type, or a file at or over 600 KB. The offending paths are listed under the messageleed site validate --reset restores them. --reset --dry-run lists what it would restore first
Issues were encountered and validation failed!leed site validateThe closing line after any validation failureRead the lines above it
You must run the following command before changes can be committed:leed site commitOne or more modified files have not been validated since you last edited themRun leed site validate
You must run the following command before changes can committed:leed site commitYour last recorded successful build is older than your newest validated edit. The missing “be” is in the product — quoted here so a search matchesRun a plain leed site build, or --serve. A build you ran with -d will not satisfy this
You are only allowed on specific branches. Change back to staging!leed site commit, leed site pushYou are on a branch other than staginggit checkout staging
Run the following commands to complete your setup:leed site commit, leed site push, leed site initGit has no user.name / user.email on this machine, or one is blankRun the two git config --global lines it prints
No changes have been committed. Nothing to push.leed site pushNothing is ahead of origin/stagingNothing to do — and note this exits 0
Could not fetch from origin:leed site pushThe fetch before the rebase failed, usually authenticationleed auth rotate is the hint the command gives
Remote has conflicting changes that can't be auto-rebased.leed site pushSomeone — or the CMS — pushed something your commits cannot replay ontoResolve it by hand, then push again
An error occurred while pushing:leed site pushGit refused the push itselfRead the git output beneath it. If it reads as a credential refusal, leed auth rotate
The CMS was not notified:leed site pushThe commits reached origin/staging and the CMS callback did not. Exit 4 or 5See below — this one has a specific recovery
No HEAD commit recorded. Run 'leed site commit' first.leed site notify-cmsThere is no recorded commit set to reportCommit through the CLI first

A rebase conflict aborts the rebase and leaves your work intact. The recovery is ordinary git:

cd ~/leed/<your-domain-slug>/raw-content
git pull --rebase
# resolve the conflicts, then:
git rebase --continue
leed site push

That git pull --rebase is explicitly permitted by the hooks. Why the repository rebases rather than merges, and who else writes to it, is on Git workflow. Which files validation protects, and why some are restored on sight, is on editable and read-only files.

Build problems

Message (verbatim)CommandCauseFix
bun install failed in build output directory: <path>leed site build --wrangler, CI buildsDependency installation into the generated worker directory failedRead the stderr it prints. Usually a registry token — leed auth rotate
No wrangler.jsonc found in <dir>. Run 'leed site build' first.leed site uploadThere is no build output to uploadBuild first
[site-build] … on a deployment rownone — this appears in the CMSYour content or a template failed to render during the CMS-side buildReproduce it locally with leed site build, then read when a deployment fails

Build failure output is summarized rather than dumped, which is worth knowing before you conclude that something was swallowed. The CLI scans the subprocess output for lines carrying ✘, [ERROR], a leading Error:, Failed: or fatal:, and keeps each such line together with the indented lines that follow it. When it finds no marker at all it falls back to the raw output. Either way the result is trimmed to its last 2,000 characters, so a very long failure is shown from its end, not its beginning. If that is not enough, re-run with --verbose for the CLI’s own channels, or DEBUG=Eleventy* leed site build for Eleventy’s internals. Both, and the recording rule that decides whether a build counts, are on leed site build.

OpenAPI import problems

Message (verbatim)CommandCauseFix
OpenAPI spec not found: <path>leed import openapiThe path does not exist. Checked before the engine opens anythingCorrect the path
OpenAPI spec must end with one of .json, .yaml, .yml: <path>leed import openapiUnrecognized extensionRename or convert the spec
Could not determine the page type slug.leed import openapi, markdown, htmlNo local pageTypeList.json entry for that page type idCreate it with leed settings pagetype create, then pass --page-type-path on the first staging run
Generated OpenAPI spec file(s) exceed the 26214400-byte limit and cannot be published.leed import openapiA generated spec file is over 25 MBSplit the API across more than one page type
The staged page set is not intact — aborting before any CMS write:leed import openapi, leed import applyAn integrity check on the page set failed. One indented detail line per problem followsFix the source (or the staged menu), then stage again — leed import clean first if a set is already staged
Nothing to import for this page type.leed import applyNothing is staged for that page type idStage first with leed import openapi, markdown or html
The OpenAPI spec changed or is missing since you staged it.leed import applyThe spec file moved, or its bytes changed, since staging recorded its SHA-256Re-run leed import openapi, then build again
Build and review the generated set before importing. …leed import applyNo recorded build is newer than the staging run. The message names which case you are in and prints both timestampsRun leed site build, or --serve to review it. A build you ran with -d is not recorded
Some pages failed. Re-run 'leed import apply' with the same flags to retry — progress is tracked in:leed import applyA partial importRe-run the same command; acknowledged pages are not re-sent
Menu not yet applied.leed import applyThe pages landed, the navigation did not. Treated as incompleteRe-run the same command
Failed to stage OpenAPI spec files: …leed import applyThe page import succeeded; staging the raw spec files did notRe-run leed import apply — it retries only the staging

The --debug trap deserves repeating because it looks like the CLI is ignoring you: a build you run with -d is deliberately not recorded as a successful build, so a developer with -d in their muscle memory can build ten times and still be told to build. (--serve skips minification by itself and still counts.) The whole workflow, with the two guards drawn as a diagram, is on importing an OpenAPI spec; the flags are on OpenAPI commands.

Eject problems

Message (verbatim)CauseFix
No templates are available to customize on your current plan.Every ejectable template is above your tier. Tier resolution is fail-closed, so an unknown plan lists nothingUpgrade, or leave the defaults in place
Unknown template '<name>'. Valid names: …A typo, or a template that does not existRun leed site eject with no argument to list what is available
'<name>' requires the <tier> plan or above; this site is on <tier>.The template exists but is gated. Gated templates are hidden from the listing, so you only see this if you named oneUpgrade
<path> already exists. Pass --force to overwrite your customization.You have already ejected this template and edited itNothing, unless you want to discard your version — then --force
The Leed default for '<name>' is missing at <path>The CLI’s own copy of the source template is absentRe-run the installer to repair the installation

Each template, what it controls and what you take responsibility for by ejecting it are on leed site eject.

Installer problems

The one-line installer runs under set -euo pipefail, so any of these ends the run with exit 1.

Message (verbatim)CauseFix
curl is required but not found.No curl on the machineInstall it. You will have seen this only if you piped the script in some other way
git is required but not found. Install from https://git-scm.com/No gitInstall git and re-run
bun v1.1.0 or later is required.You declined the offer to update bunRe-run and accept, or update bun yourself
bun is required. Install manually from https://bun.sh/You declined the offer to install bunRe-run and accept, or install it from bun.sh
Could not start device authorization (HTTP <status>) after 4 attempts. Check your network and try again.Every retry of the first sign-in request failedCheck connectivity, then re-run
Cannot create directory: <path> / Directory is not writable: <path>The chosen install directory cannot be created or written toPick a different directory, or fix the permissions
Re-run the installer and give an absolute path.You declined to use the absolute path a relative entry resolved toRe-run and type an absolute path
Authorization timed out after 5 minutes. Run the installer again to retry.The device code was never approved in the browserRe-run the installer and approve promptly
Authorization code expired. Run the installer again to retry.The code expired before it was approvedRe-run
Authorization was denied.You clicked Deny on the approval screenRe-run
The sign-in was not accepted (HTTP 401). Re-run the installer and sign in again.Step 4 refused the sessionRe-run and sign in again
… is not authorized to download configuration (HTTP 403). The account needs developer access to that company.The named account lacks the developer override for that companyAsk an administrator, then re-run
This sign-in is not bound to a company — the account belongs to more than one. …HTTP 422: no company was chosen for the sign-inRe-run and choose the company, on the approval page if signing in
Could not reach <url> (no response within 20s). Check your network and try again.The configuration download timed out. It is never retried, because the timed-out request may still have minted tokensCheck connectivity, then re-run
Failed to download configuration (HTTP 500) — this is a server-side error, not a problem with your access.The CMS failed to produce your configuration. The server’s own response is printed underneathReport it with the response text
Installation failed — leed command not found.The global install completed but leed is not on your PATHCheck where bun puts global binaries and add it to your PATH
@leed registry in <path> uses an environment variable — leaving it aloneNot an error. Your ~/.bunfig.toml references a $VAR for the @leed scope, so the installer will not overwrite what you are managing yourselfNothing

Re-running the installer is safe and idempotent — it reuses valid credentials, skips prerequisites that are already satisfied, and asks before overwriting an existing leed.config.json. The full walkthrough is on installing the Leed CLI.

When there is no document at all

Under --json the contract is exactly one JSON document on stdout, and the three paths that used to break it are closed. An interrupted leed site build --wrangler --json now writes its document, with the interruption in warnings, and exits 130 on Ctrl+C or 143 on SIGTERM. A Tailwind failure during a --json build is reported as a failure document instead of exiting with an empty stdout. And a failure after the action has already emitted no longer appends a second envelope.

An empty stdout therefore means the process was killed outright — by the out-of-memory killer, a forced kill, or a CI runner timing out — before it could write anything. Treat it as a failure, and read stderr for what the process managed to say.

What the document is supposed to look like is on machine-readable output (--json).

When nothing else works

Three repairs, in increasing order of how much they touch. Try them in order.

If none of that helps, a useful report has three things in it: the output of leed --version, the exact command you ran, and the run repeated with --verbose so the leed:* diagnostic channels are on. Add --json if the failure is in a script — error.code and error.details say more than the rendered text does.

When the failure is a wrong flag rather than a wrong state, the command’s signature is at CLI command reference, and every path and variable named on this page is cataloged on leed.config.json, files and environment. Errors from the rest of the product — the CMS, the editor, the site build — are collected in common error messages, which links back here rather than duplicating this catalog.

ESC