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 | General failure — validation failed, restricted files, wrong branch, an authentication failure, a rebase conflict, a failed push, an unknown or gated eject template, a failed page-type create or list, a partial import, an unrecognized flag | every command, plus the argument parser | Read error.message, or the text above the exit |
1 | true | leed site upload only: the worker version was uploaded and the CMS build-result callback afterwards was not delivered | leed site upload | Re-run leed site upload once the CMS is reachable. The explanation is in the document’s warnings array. This row is why nothing may branch on the number |
2 | false | A hard pre-flight refusal in a CI-shaped command: LEED_API_TOKEN missing in CI, no environment in leed.config.json, or no stored credentials | leed site upload, leed site notify-cms, and leed site push by way of its notification step | Fix the named precondition. Nothing was sent |
8 | false | leed site notify-cms could not connect to the CMS at all | leed site notify-cms, and leed site push | The CMS never heard about your commits. See below |
9 | false | leed site notify-cms reached the CMS and got a non-200 answer | leed site notify-cms, and leed site push | Same as 8 |
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 |
0 | no document is written | A leed site build --wrangler --json run that was interrupted with Ctrl+C. The signal is forwarded, the process exits 0, and stdout is empty | leed site build --wrangler | A known defect, not a contract. See when there is no document at all |
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, and it is not what older copies of the bundled skill reference say.
The exit code is 0 on success and non-zero on failure. Branch on
ok, or on zero versus non-zero — never on the specific number. A command may exit non-zero while reportingok: truewhen a non-fatal step failed after the result was already determined; the detail is inwarnings.
The specific numbers in the table above are deliberate and are staying, because customer scripts already branch on them. That is exactly why your script must not join them: notify-cms reports an HTTP error as 9 and a connection failure as 8, never as 1; the CI-shaped commands refuse with 2; and leed site upload has the exit-1-with-ok: true path. A script that tested if [ $? -eq 1 ] would misread the three most common failures as successes.
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 fourteen codes 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 rewrite your bunfig and your repository’s origin | cd into the site folder |
| `Could not determine the target environment. Run from inside a leed-managed site directory (leed.config.json) or set ENV_NAME=dev | staging | production.` | push, commit, import, create-page-type, list |
Not authenticated for <env>. Run: leed auth login --env <env> | any authenticated command | No credentials block stored for the resolved environment | Log in for that environment, not another one |
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 |
Not authorized to download configuration (HTTP 401). Your account needs developer access. | the installer | Step 4 of the installer was refused | Same — then re-run the installer |
(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 token in your repository’s origin URL is the stale one — the three-credential model and the fix are on CLI authentication.
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 500 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. A --debug build 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 push, leed site init | Git has no user.name / user.email on this machine | 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 |
The CMS was not notified: | leed site push | The commits reached origin/staging and the CMS callback did not. Exit 2, 8 or 9 | 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>/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 -v for the CLI’s own channels, or DEBUG=Eleventy* leed site build for Eleventy’s internals. Both flags, and the recording rule that decides whether a build counts, are on leed site build.
OpenAPI import problems
| Message (verbatim) | Command | Cause | Fix |
|---|---|---|---|
--openapi file not found: <path> | leed site generate | The path does not exist. Checked before the engine opens anything | Correct the path |
--openapi file must end with one of .json, .yaml, .yml: <path> | leed site generate | Unrecognized extension | Rename or convert the spec |
Could not determine the page type slug. | leed site generate | No local pageTypeList.json entry for that page type id | Run leed site create-page-type, or pass --page-type-path |
Generated OpenAPI spec file(s) exceed the 26214400-byte limit and cannot be published. | leed site generate | A generated spec file is over 25 MB | Split the API across more than one page type |
The generated OpenAPI page set is not intact — aborting before any CMS write: | leed site generate, leed site import | An internal integrity check on the generated set failed. One indented detail line per problem follows | Re-generate. If it recurs, the spec itself is the suspect |
Nothing to import for this page type. | leed site import | No manifest exists for that page type id | Run leed site generate first |
The OpenAPI spec changed or is missing since you generated. | leed site import | The spec file moved, or its bytes changed, since generate recorded its SHA-256 | Re-run generate, then build again |
Build and review the generated set before importing. … | leed site import | No recorded build is newer than the generate. The message names which case you are in and prints both timestamps | Run a plain leed site build. --debug builds are not recorded |
Some pages failed. Re-run 'leed site import' with the same flags to retry — progress is tracked in: | leed site import | A partial import. Exit 1 | Re-run the same command; acknowledged pages are not re-sent |
Menu not yet applied. | leed site import | The pages landed, the navigation did not. Treated as incomplete | Re-run the same command |
Failed to stage OpenAPI spec files: … | leed site import | The page import succeeded; staging the raw spec files did not | Re-run leed site import — it retries only the staging |
The --debug trap deserves repeating because it looks like the CLI is ignoring you: leed site build --debug 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. 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 |
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 |
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 was denied. | You clicked Deny on the approval screen | Re-run |
Not authorized to download configuration (HTTP 403). Your account needs developer access. | Your account lacks the developer override | Ask an administrator, 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 one JSON document on stdout. Three paths break it today, and all three are reachable on ordinary work, so they are written here as “if you see this, here is what happened” rather than left for you to debug.
Empty stdout, exit 0. You interrupted leed site build --wrangler --json with Ctrl+C. The signal is forwarded to the wrangler process group, the templates are cleaned up, and the process exits 0 having written nothing. There is no failure to report and no document to parse — treat an empty stdout from that command as “canceled”, not as success.
Empty stdout, exit 1. A Tailwind compilation failure during a --json build exits the process directly, before any document is written. The compiler’s own error goes to stderr; read it there.
Two documents on stdout, so JSON.parse fails. A command emitted its document and then something after the action threw — most reachably, writing the validation-tracking file failed on a read-only or full disk during leed site build --json. The failure is appended as a second envelope. If a parse fails, look at the raw bytes before assuming the first document was wrong: the build may well have succeeded.
None of the three is part of the contract; all three are tracked as defects and are carried on known limitations. 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 -V, the exact command you ran, and the run repeated with -v 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.