# Troubleshooting

> AstroCraft's failure modes: the ones it produces on purpose, the ones that look like bugs and are not, and those whose message names the wrong cause.

Source: https://editor.astrocraftthemes.com/docs/troubleshooting/

Most of what goes wrong here goes wrong loudly, at build time, with the offending value named. That is a design choice. This page collects the messages you will actually see, plus the behaviours that look like bugs and are not.

The hardest cases are the few where the error names something other than the cause, so those come first.

## Errors that name the wrong thing

**246 errors on your first `pnpm check`**, mostly `ts(2307) Cannot find module 'vitest'` and `ts(7006)` implicit-any. The copied source brought its tests and no runner is installed. Nothing in that output contains the word _vitest_. `pnpm add -D vitest happy-dom` and copy `vitest.config.ts`, or delete the tests deliberately.

**`[MISSING_EXPORT] "RESEND_API_KEY" is not exported by "\0astro:env/server"`.** The `env.schema` block is missing from `astro.config.mjs`. The _values_ are optional; the schema is structurally required, because two actions import from it.

**The test suite reports your collections as empty**, around thirty failures, nothing pointing at the cause. `cacheDir: './.astro/'` is missing. Astro writes the content store to `.astro/` in dev and to `cacheDir` everywhere else, so `astro sync` and Vitest fill and read different files.

**`ERR_MODULE_NOT_FOUND` from the accounts CLI.** You are on v1.0.0, which shipped an aliased import in the one file that must not have one. 1.0.1 is the fix and the reason to take it.

**`ts(7006)` on an unrelated `.map()` in an `.astro` file.** A second HTML-tag-shaped token in that file's **frontmatter comments** costs it its `Props` type, which degrades to `any`. The tell is a `ts(6196)` on `Props`. Template `<!-- -->` comments are unaffected.

**`Cannot find package 'unstorage'` during a publish build.** The publish gate builds into a throwaway directory under `node_modules/` for exactly this reason — Astro runs its prerender entry from inside the output directory and Node walks up looking for `node_modules`. If you have moved `outDir`, move `build.client` and `build.server` with it; Astro derives them at config resolution, so moving one alone makes the build log a new directory and write to the old one.

## Install and mount

**`git clone` fails.** The GitHub invitation from your purchase has not been accepted. It looks exactly like no purchase.

**The dev server serves a site with no CMS in it**, often with a missing-file error naming a file that is plainly there. It was running before you mounted. Astro reads its config once at startup — restart it.

**A newly added admin route 404s under `astro dev`.** The route table is scanned at config load, not per request. Restart. Editing an existing route still hot-reloads.

**The build refuses and names a route.** You set `output: 'static'`, or an admin route is on the wrong side of the on-demand line. Both directions are refused on purpose — see [Mounting](/docs/mounting/).

**The build throws naming `admin.config.ts` and a key.** The runtime validator. The two mistakes that typecheck perfectly are a `domain` written as a URL, and a collection key that disagrees with its own `name`.

## Content and the editor

**The editor throws naming a collection and `z.object()`.** Your schema is a function of `SchemaContext` — the `({ image }) => z.object({…})` form. It has no `.shape` until it is called. See [What the editor reads from Zod](/docs/schema-fields/).

**A field renders read-only with a reason.** The walker declined to guess. That is the only behaviour that does not eventually corrupt frontmatter. The Schema tab shows the reason per field.

**A paragraph field is a single-line input.** The textarea heuristic did not fire — it is a name list plus a `.max()` over 160.

**Save says the entry changed on disk.** Somebody else edited it, or you have two tabs open. Your work is still banked as a draft, and History has the last twenty saves.

**A renamed entry 404s.** The content store has not re-synced. Restart the dev server; under a production build the route exists at the next build. The rename already committed.

**Two of the four status filters are always empty.** `Ready` and `Needs review` describe git state. `Needs review` is unreachable until a pull-request flow exists, and the tab is drawn rather than faked from a field that means something else.

**A relative date on a collection list looks frozen.** It is — that one module builds its rows at module scope, so on a long-lived server the label is measured from boot. Harmless in dev, which restarts constantly. A known defect.

## Publishing

**Publish says "no identity".** `GIT_COMMITTER_NAME` / `GIT_COMMITTER_EMAIL` are unset on the container.

**A push is rejected.** The next step is on screen: _Update from the site's repository_. Only genuine divergence still needs a terminal.

**Publish is refused and names a field.** Schema validation on an outgoing entry. A `draft: true` entry still blocks, because it rides the branch.

**Publish is refused and names the licence.** No key, an expired key, or a key Polar has stopped granting. An outage is _not_ one of these — it rides a fourteen-day grace.

**Publish → rejected → sync → publish again waits 60 seconds.** The per-account cooldown, deliberately not cleared on rejection: clearing it would let a diverged checkout rebuild forever.

**"Your changes are live" never arrives.** Either the production build is slow, or the page answering `/` has no `<meta name="build-rev">`. Publishing worked regardless.

**The unpushed count never drains.** Auto-push is failing. It is a log line rather than a dialog, so the log is where the reason is.

**Drafts show on production, or do not show on staging.** `PREVIEW=1` is on the wrong deploy. It belongs on staging and nowhere else.

## Deployment

**Clone fails with _Remote branch staging not found_.** Run `git push origin main:staging` once.

**A redeploy logged everyone out and reset settings.** The volume did not survive, or is not mounted at `/data`. The store lives inside the checkout unless you moved it to a database.

**A database URL in `.env` did nothing.** Those two variables are read from `process.env`; a `.env` file fills `import.meta.env`. Export them in the shell.

**The CMS throws on first load naming both database variables.** The URL is unreachable or the token is wrong, and it will not fall back to files — an empty store looks exactly like a fresh install, and a fresh install re-opens sign-up to whoever loads the page first.

**The container serves a stale public site.** Its own copy rebuilds at boot, not on push. The admin, the previews and the live check are unaffected.

## Accounts

**Sign-in does not know an account you just created, or you are offered first-admin sign-up again.** The `.accounts/` store is resolved against `process.cwd()`, not against your project. A server or CLI started from another directory reads an empty store there, which is indistinguishable from a fresh install. Run both from the project root and check the path the CLI prints. Look for a stray `.accounts/` wherever you started from.

**Sign-up says accounts cannot be created right now.** Mail is not configured. This is permanent, not transient. Use the CLI.

**Somebody signed up and cannot sign in.** Sign-up grants **No access** on purpose. Promote them from the team screen.

**Locked out with no admin at all.** Run the accounts CLI for your own address with no third argument; it defaults to `admin` and replaces the role.

**Signed in and getting 429s.** Either the 10-attempts-in-15-minutes lockout, or the burst shed at four concurrent sign-ins. Both send a real `Retry-After` and the screen draws a countdown.

## Composer

**A page opens read-only.** Single-quoted imports in the frontmatter, a construct the printer does not emit, or genuinely hand-written. The message names the line it stopped at. Hand-written pages listing as read-only is the honest answer, not a failure.

**A section shows a labelled placeholder.** Its component is gone from disk, or predates this deployment's bundle. The row survives so the counter cannot lie.

**The stage refuses and cannot find the section boundary.** One root per section is the ceiling — a layout with siblings beside its `<slot />` breaks the walk, and the stage says so rather than guessing.

**A section's script stopped working after an edit.** A patched-in section's hoisted script does not re-run. It stays un-enhanced until the next real navigation.

**Closing the tab is refused.** Unsaved changes. Undo to clean, or save.

## Things that are working as designed

- The image library reporting every file unreferenced and missing alt text **on the demo content**. Nothing outside the CMS's own source references those files. Your repository gets real numbers.
- The status pill being invisible below the `lg` breakpoint. A known gap; the work still happens.
- The UI primitives saying "Show"/"Hide" in English in every language. They are outside the translation scan.
- Undo taking two presses to get back to a literal markdown prefix. Snapshots, not inverse operations.
- Publish refusing rather than queueing when one is already in flight.