Ovellum v0.21.0
English

Edited

Troubleshooting#

The problems people actually hit, with the cause and the fix. Most surface as a clear CLI message — this page explains what each one means.

Protected zones aren't merging#

In hybrid mode, hand-written prose only survives a rebuild when it's inside a @manual zone keyed to a generated anchor:

<!-- @manual:start id="src/utils/format.ts::padZero" -->
Your prose here — kept verbatim across every rebuild.
<!-- @manual:end -->
  • No id? Without id="…", Ovellum synthesizes a positional fallback and warns: protected zone "manual-block-1" uses a positional fallback id. Add id="…" on the <!-- @manual:start --> tag so the block survives reordering. The block still merges, but it's tied to its position, not a symbol — add the id so reordering the file doesn't move your prose onto the wrong section. The id matches the anchor comment the generator emits: <!-- ovellum:anchor id="src/utils/format.ts::padZero" … --> (the format is <sourceFile>::<symbol>).

  • ovellum check --strict reports id-less zones as positional-zone and zones whose anchor no longer exists as stale-anchor, so you can catch both in CI before they bite.

Unbalanced @manual tags#

These are hard errors (the build stops with exit code 3):

MessageCause
Nested @manual:start tag at line N (previous opened at line M).A second @manual:start before closing the first.
Stray @manual:end at line N: no matching @manual:start.An @manual:end with no opener above it.
Unclosed @manual:start at line N.A zone that's never closed — add a matching <!-- @manual:end -->.

Every @manual:start needs exactly one @manual:end, and zones can't nest.

My prose disappeared after a rebuild (orphans)#

It didn't — it was quarantined, not dropped. When the symbol a zone was attached to is renamed or deleted, its anchor vanishes, so the merger moves the prose to .ovellum/orphans/ (the protect.orphanDir) rather than lose it. The build summary lists it under quarantined: with a ↪ <path> line.

Recover it:

ovellum orphans                 # list quarantined prose + whether its anchor is back
ovellum orphans --stale         # only those older than protect.orphanRetention (default 90 days)
ovellum orphans --reattach      # interactively reattach, delete, or skip each

ovellum orphans shows each block's source doc, age, and anchor statuspresent again in source — reattachable, gone from current source, or unknown (no IR snapshot — run ovellum build). The status is read from the .ovellum/ir.json snapshot a build writes, so run a build first if it says unknown.

--reattach is interactive (run it in a terminal). For each orphan it offers to reattach to the anchor that's back in the source (or a likely rename, by name similarity), reattach to a different anchor, delete, or skip. Reattaching splices the prose into a fresh @manual zone under the target and removes the archive. ovellum diff is the companion that flags likely renames.

My config isn't being used#

A few things to check:

  • Wrong place / name. The file must be ovellum.config.{ts,mts,cts,js,mjs,cjs,json} at the project root (discovery is via c12). A missing config is not an error — Ovellum falls back to built-in defaults and the build summary prints config: (defaults). If you see that line, your file wasn't found. Pass --config <path> to point at it explicitly, or --cwd <dir> if you're running from elsewhere.

  • Invalid config stops the build with config error: … and exit code 3 — e.g. `mode` must be one of: hybrid, manual, auto. The message names the offending field; fix it and rebuild.

  • import { defineConfig } from 'ovellum' fails to resolve. A value import needs the ovellum package installed where the config loads. The scaffolded config deliberately avoids that — it uses import type { OvellumUserConfig } from 'ovellum' plus export default { … } satisfies OvellumUserConfig, which is erased at load so the file has no runtime dependency. Either install ovellum locally, or use the import type + satisfies form.

Almost always a relative path. Pages get pretty URLs (guides/install.md/guides/install/), so a relative architecture.svg resolves against /guides/install/, not the folder the file lives in. Use root-absolute paths:

![Architecture](/guides/architecture.svg)   <!-- ✓ always resolves -->
![Architecture](architecture.svg)            <!-- ✗ resolves against the page URL -->

Other asset gotchas:

  • site.basePath must start with / and have no trailing slash ('/ovellum'). It's prepended to every internal link + asset path at render time, so you keep authoring root-relative links. A bad value fails validation: `site.basePath` must start with `/` … or be the empty string.
  • public/ maps to the site rootpublic/favicon.ico/favicon.ico. Reference those at the root, without the public/ prefix.
  • ovellum check validates page links, not asset URLs. Images and downloads point at files, not pages, so confirm them with a local ovellum serve (or ovellum dev). See Assets.
  • Using a CDN? site.assetBaseUrl rewrites public/ references — but not query-stringed or srcset URLs; reference those by their final CDN URL.

ovellum dev says it only supports manual mode#

It does — dev is the manual-mode live-preview loop. For auto/hybrid, run ovellum watch (rebuilds the Markdown on every change) and serve the output yourself, or just ovellum build. The message spells out the alternative.

Exit codes#

Stable across commands, so CI can branch on them:

CodeMeaning
0Success — built, or check/diff found nothing.
1Issues found (check broken links, diff --exit-code changes) or a build error.
2ovellum init — config already exists; re-run with --force.
3ConfigError — config invalid or failed to load.
130Canceled at an interactive prompt (Ctrl-C).

In --json mode a ConfigError is emitted on stdout as { "ok": false, "error", "hint" } — see Automation.

Still stuck?#

Run the command with --verbose for the config-resolution path and per-stage detail (it goes to stderr, so it composes with --json), and check the CLI reference for the full flag set. If it looks like a bug, open an issue.

Edit this page