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
1falseGeneral 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 flagevery command, plus the argument parserRead error.message, or the text above the exit
1trueleed site upload only: the worker version was uploaded and the CMS build-result callback afterwards was not deliveredleed site uploadRe-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
2falseA hard pre-flight refusal in a CI-shaped command: LEED_API_TOKEN missing in CI, no environment in leed.config.json, or no stored credentialsleed site upload, leed site notify-cms, and leed site push by way of its notification stepFix the named precondition. Nothing was sent
8falseleed site notify-cms could not connect to the CMS at allleed site notify-cms, and leed site pushThe CMS never heard about your commits. See below
9falseleed site notify-cms reached the CMS and got a non-200 answerleed site notify-cms, and leed site pushSame as 8
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
0no document is writtenA leed site build --wrangler --json run that was interrupted with Ctrl+C. The signal is forwarded, the process exits 0, and stdout is emptyleed site build --wranglerA known defect, not a contract. See when there is no document at all
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, 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 reporting ok: true when a non-fatal step failed after the result was already determined; the detail is in warnings.

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'
fi

Parse 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)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 rewrite your bunfig and your repository’s origincd 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=devstagingproduction.`push, commit, import, create-page-type, list
Not authenticated for <env>. Run: leed auth login --env <env>any authenticated commandNo credentials block stored for the resolved environmentLog in for that environment, not another one
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
Not authorized to download configuration (HTTP 401). Your account needs developer access.the installerStep 4 of the installer was refusedSame — then re-run the installer
(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 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)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 500 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. A --debug build 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 push, leed site initGit has no user.name / user.email on this machineRun 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
The CMS was not notified:leed site pushThe commits reached origin/staging and the CMS callback did not. Exit 2, 8 or 9See 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>/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 -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)CommandCauseFix
--openapi file not found: <path>leed site generateThe path does not exist. Checked before the engine opens anythingCorrect the path
--openapi file must end with one of .json, .yaml, .yml: <path>leed site generateUnrecognized extensionRename or convert the spec
Could not determine the page type slug.leed site generateNo local pageTypeList.json entry for that page type idRun 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 generateA generated spec file is over 25 MBSplit 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 importAn internal integrity check on the generated set failed. One indented detail line per problem followsRe-generate. If it recurs, the spec itself is the suspect
Nothing to import for this page type.leed site importNo manifest exists for that page type idRun leed site generate first
The OpenAPI spec changed or is missing since you generated.leed site importThe spec file moved, or its bytes changed, since generate recorded its SHA-256Re-run generate, then build again
Build and review the generated set before importing. …leed site importNo recorded build is newer than the generate. The message names which case you are in and prints both timestampsRun 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 importA partial import. Exit 1Re-run the same command; acknowledged pages are not re-sent
Menu not yet applied.leed site importThe pages landed, the navigation did not. Treated as incompleteRe-run the same command
Failed to stage OpenAPI spec files: …leed site importThe page import succeeded; staging the raw spec files did notRe-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)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
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
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 was denied.You clicked Deny on the approval screenRe-run
Not authorized to download configuration (HTTP 403). Your account needs developer access.Your account lacks the developer overrideAsk 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 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 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.

ESC