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
| Code | ok in the JSON document | Meaning | Emitted by | What to do |
|---|---|---|---|---|
0 | true | Success. Also “No changes have been committed. Nothing to push.”, a completed --dry-run, and an empty commit set on notify-cms | every command | Nothing. Note the second column of meanings if you are scripting: a no-op is a success |
1 | false | Unclassified — an unexpected fault in the CLI itself (internal_error) | every command | Read the error, and report it with the whole document |
2 | false | Usage — 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 parser | Fix the command line. leed schema <command> --json gives the real flags |
3 | false | Precondition unmet — validation, restricted files, wrong branch, a failed build, a rebase conflict, a refused git command, a partial upload, not in a site folder | every command | Follow error.hint |
4 | false | Transient — the CMS or the git host could not be reached, or answered with a server error (network_error) | every command that talks to the network | Retry with backoff |
5 | false | Needs a person — not signed in, session expired, permission denied, a confirmation or an interactive terminal required | every command | Stop and ask. Retrying fails identically |
| non-zero | true | The 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 class | those commands | Read warnings. This row is why nothing may treat ok and the exit code as one signal |
18 | no document is written | A git hook rejected the command. Always husky, never the CLI itself | .husky/pre-commit, pre-push, commit-msg | Use leed site commit / leed site push instead of raw git |
130 / 143 | true, with a warning | A leed site build --wrangler session interrupted with Ctrl+C or SIGTERM | leed site build --wrangler | Nothing; you stopped it |
1 | n/a | The one-line installer’s fatal(), under set -euo pipefail | the installer shell script | Read 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: truewhen a non-fatal step failed after the result was already determined; the detail is inwarnings.
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'
fiParse 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) | Command | Cause | Fix |
|---|---|---|---|
| Site configuration not initialized. Run `leed site init`!!! | any leed site command | leed.config.json still has initialized: false | Run leed site init |
| Site configuration already complete! Try running a build `leed site build` | leed site init | The site is already set up in this folder | Nothing is wrong. Build instead |
Could not locate leed.config.json. Run this application from the directory where your repo exists. | any leed site command | You are not in the site folder or in raw-content/; the CLI looks in exactly those two places | cd 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 reset | Both are site-scoped — they rotate the tokens of the site you are standing in | cd 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 commands | No leed.config.json names an environment and ENV_NAME is unset or invalid. These commands refuse rather than assume production | Run 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 command | No session stored for the resolved environment and company | Log 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 logout | You hold sessions for several companies, none is the default, and nothing names one | Pass --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 login | The approval bound the session to no company — the account belongs to several and none was picked. The session is kept as an unassigned legacy session | Sign in again with --company |
The stored session for <env> belongs to company <id>, not <id>. | leed site init | The configuration is for a different company from the session you hold | leed 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 it | Fix 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 push | The repository token reaches git through config environment variables that older git ignores | Upgrade git |
Not authenticated. | leed site init | No credentials at all | leed auth login --browser --env <env> |
Session expired or invalid. | leed site init, leed auth rotate | The stored token is past the refresh grace window | Log in again |
Session expired. Run: leed auth login --env <env> | leed auth status | The developer-credentials check got a 401 | Log in again |
No active organization. Download a fresh leed.config.json from the CMS. | leed site init | The session carries no active organization | Re-download the config from Developer Setup |
You don't have developer access to this organization. | leed auth rotate, leed auth reset | Your account lacks the developer override | Ask an administrator to grant it |
No developer access for this organization | leed auth status | The same 403, reported in the status listing | Same |
(no tokens stored yet — run 'leed auth rotate' or rerun the installer) | leed auth status | You are signed in, but no GitLab tokens have been issued to this machine | leed auth rotate |
Not logged in to <env>. | leed auth logout | Nothing was stored for that environment | Nothing 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) | Command | Cause | Fix |
|---|---|---|---|
Error: Restricted file violation! | leed site validate | You 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 message | leed site validate --reset restores them. --reset --dry-run lists what it would restore first |
Issues were encountered and validation failed! | leed site validate | The closing line after any validation failure | Read the lines above it |
You must run the following command before changes can be committed: | leed site commit | One or more modified files have not been validated since you last edited them | Run leed site validate |
You must run the following command before changes can committed: | leed site commit | Your last recorded successful build is older than your newest validated edit. The missing “be” is in the product — quoted here so a search matches | Run 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 push | You are on a branch other than staging | git checkout staging |
Run the following commands to complete your setup: | leed site commit, leed site push, leed site init | Git has no user.name / user.email on this machine, or one is blank | Run the two git config --global lines it prints |
No changes have been committed. Nothing to push. | leed site push | Nothing is ahead of origin/staging | Nothing to do — and note this exits 0 |
Could not fetch from origin: | leed site push | The fetch before the rebase failed, usually authentication | leed auth rotate is the hint the command gives |
Remote has conflicting changes that can't be auto-rebased. | leed site push | Someone — or the CMS — pushed something your commits cannot replay onto | Resolve it by hand, then push again |
An error occurred while pushing: | leed site push | Git refused the push itself | Read the git output beneath it. If it reads as a credential refusal, leed auth rotate |
The CMS was not notified: | leed site push | The commits reached origin/staging and the CMS callback did not. Exit 4 or 5 | See below — this one has a specific recovery |
No HEAD commit recorded. Run 'leed site commit' first. | leed site notify-cms | There is no recorded commit set to report | Commit 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 pushThat 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) | Command | Cause | Fix |
|---|---|---|---|
bun install failed in build output directory: <path> | leed site build --wrangler, CI builds | Dependency installation into the generated worker directory failed | Read the stderr it prints. Usually a registry token — leed auth rotate |
No wrangler.jsonc found in <dir>. Run 'leed site build' first. | leed site upload | There is no build output to upload | Build first |
[site-build] … on a deployment row | none — this appears in the CMS | Your content or a template failed to render during the CMS-side build | Reproduce 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) | Command | Cause | Fix |
|---|---|---|---|
OpenAPI spec not found: <path> | leed import openapi | The path does not exist. Checked before the engine opens anything | Correct the path |
OpenAPI spec must end with one of .json, .yaml, .yml: <path> | leed import openapi | Unrecognized extension | Rename or convert the spec |
Could not determine the page type slug. | leed import openapi, markdown, html | No local pageTypeList.json entry for that page type id | Create 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 openapi | A generated spec file is over 25 MB | Split 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 apply | An integrity check on the page set failed. One indented detail line per problem follows | Fix 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 apply | Nothing is staged for that page type id | Stage first with leed import openapi, markdown or html |
The OpenAPI spec changed or is missing since you staged it. | leed import apply | The spec file moved, or its bytes changed, since staging recorded its SHA-256 | Re-run leed import openapi, then build again |
Build and review the generated set before importing. … | leed import apply | No recorded build is newer than the staging run. The message names which case you are in and prints both timestamps | Run 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 apply | A partial import | Re-run the same command; acknowledged pages are not re-sent |
Menu not yet applied. | leed import apply | The pages landed, the navigation did not. Treated as incomplete | Re-run the same command |
Failed to stage OpenAPI spec files: … | leed import apply | The page import succeeded; staging the raw spec files did not | Re-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) | Cause | Fix |
|---|---|---|
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 nothing | Upgrade, or leave the defaults in place |
Unknown template '<name>'. Valid names: … | A typo, or a template that does not exist | Run 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 one | Upgrade |
<path> already exists. Pass --force to overwrite your customization. | You have already ejected this template and edited it | Nothing, 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 absent | Re-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) | Cause | Fix |
|---|---|---|
curl is required but not found. | No curl on the machine | Install 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 git | Install git and re-run |
bun v1.1.0 or later is required. | You declined the offer to update bun | Re-run and accept, or update bun yourself |
bun is required. Install manually from https://bun.sh/ | You declined the offer to install bun | Re-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 failed | Check connectivity, then re-run |
Cannot create directory: <path> / Directory is not writable: <path> | The chosen install directory cannot be created or written to | Pick 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 to | Re-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 browser | Re-run the installer and approve promptly |
Authorization code expired. Run the installer again to retry. | The code expired before it was approved | Re-run |
Authorization was denied. | You clicked Deny on the approval screen | Re-run |
The sign-in was not accepted (HTTP 401). Re-run the installer and sign in again. | Step 4 refused the session | Re-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 company | Ask 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-in | Re-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 tokens | Check 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 underneath | Report it with the response text |
Installation failed — leed command not found. | The global install completed but leed is not on your PATH | Check where bun puts global binaries and add it to your PATH |
@leed registry in <path> uses an environment variable — leaving it alone | Not an error. Your ~/.bunfig.toml references a $VAR for the @leed scope, so the installer will not overwrite what you are managing yourself | Nothing |
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.