Troubleshooting

This content was generated with the assistance of AI and should be verified against the official documentation of each tool before being relied on in production.

This section’s bibliography lists the reference material consulted while preparing these pages.

This page was written against Antora 3.2.1 and checked on 2026-10-26. Each problem below was reproduced with the Pocket Units sample, a tiny units-conversion library documented from a local Git repository, and the messages are copied from the real output (the long absolute paths are shortened to /work/pocket-units-docs). The page first explains how to make Antora tell you more, then goes through the problems in the order in which a build usually meets them.

Diagnosing: log levels, exit codes and stack traces

Antora reports problems through its logger, and three settings decide how much you see and whether the build fails.

  • --log-level sets the minimum level that is printed. The default is warn, so informational messages are hidden. The choices are all, trace, debug, info, warn, error, fatal and silent.

  • --log-failure-level sets the level at which the process exits with a non-zero code. The default is fatal, which means that errors and warnings are printed but the exit code stays 0. With error or warn the build still completes and then exits with code 2. With none the exit code is 0 even after a fatal error. The same setting is available as runtime.log.failure_level in the playbook and as the ANTORA_LOG_FAILURE_LEVEL environment variable.

  • --stacktrace adds the stack trace of a fatal error, which tells you which part of Antora raised it. Every fatal message ends with the hint "Add the --stacktrace option to see the cause of the error."

  • --log-format=pretty prints readable lines and --log-format=json prints one JSON object per line, which is the default when the output is not a terminal. In CI logs the JSON form is harder to read, so pass pretty there.

The practical consequence is that a CI job that only runs npx antora antora-playbook.yml passes even when the site has broken links. A stricter job reads:

npx antora --log-format=pretty --log-failure-level=error antora-playbook.yml

The next listing is the real output of a build that contained a broken xref, run with that command. The site was written and the exit code was 2.

[11:03:44.050] ERROR (asciidoctor): target of xref not found: missing-page.adoc
    file: /work/pocket-units-docs/repo/modules/ROOT/pages/broken.adoc
    source: /work/pocket-units-docs/repo (branch: main <worktree>)

Every message names the logger (antora, asciidoctor, @antora/content-aggregator, @antora/content-classifier), the file and the content source, including the branch and whether it was read from the worktree or from the Git database. Use that source line to know which branch of which repository holds the faulty file: a fix on the wrong branch is the most common reason an error "does not go away".

The diagram summarises where each problem on this page appears in the build.

flowchart TD A["antora playbook"] --> B{"antora.yml found at each start path?"} B -- no --> C["FATAL: antora.yml not found / start path does not exist"] B -- yes --> D{"Component and version unique?"} D -- no --> E["FATAL: Duplicate nav file"] D -- yes --> F{"UI bundle available?"} F -- no --> G["FATAL: UI bundle does not exist"] F -- yes --> H["Convert pages"] H --> I["ERROR: xref or include not found"] H --> J["Site written"] J --> K["Hosting: _ folders dropped by Jekyll"]

The figure follows one build from the playbook to the published folder; the leaves are the messages described below.

Missing antora.yml

Antora only treats a folder as a component version when it contains an antora.yml. If the file is missing at a start path, the build stops at once:

[11:03:02.284] FATAL (antora): antora.yml not found in /work/pocket-units-docs/repo (branch: main <worktree>)
Add the --stacktrace option to see the cause of the error.

The message names the reference (branch, tag or worktree) that was being read, which matters when the file exists on one branch and not on another: a pattern that selects many branches can fail on an old branch that predates the docs. The usual causes are:

  • the file is named antora.yaml, Antora.yml or has another spelling;

  • the file exists in the working tree but is not committed, so a remote clone or a non-checked-out branch does not have it;

  • the file is in a subfolder, for example docs/antora.yml, and the content source does not set start_path (see the next section);

  • the branch pattern selects a branch where the documentation was not added yet. Narrow branches, or use a tag pattern, so that only references with documentation are read.

The component version descriptor itself, its required name and version keys and the nav list, is described in the component version descriptor page.

Start path mismatches

start_path (or start_paths) tells Antora in which folder of the repository the antora.yml lives. A mismatch produces one of two opposite messages, depending on which side is wrong. In the first reproduction the documentation stayed at the repository root while the playbook said start_path: docs:

[11:03:10.073] FATAL (antora): the start path 'docs' does not exist in /work/pocket-units-docs/repo (branch: main <worktree>)
Add the --stacktrace option to see the cause of the error.

In the second the files were moved into docs/ but the playbook had no start_path, so Antora looked for antora.yml at the root and printed the message from the previous section. Setting start_path: docs made the build succeed. A misspelled value (doc instead of docs) gives the same "does not exist" message as the first case.

site:
  title: Pocket Units Docs
content:
  sources:
  - url: ./repo
    branches: HEAD
    start_path: docs
ui:
  bundle:
    url: ../ui/antora-ui-default/build/ui-bundle.zip

start_paths takes a list of folders or glob patterns. A pattern that matches nothing does not fail on its own: the build only stops when none of the patterns match, with the message no start paths found in /work/pocket-units-docs/repo (branch: main <worktree>). A typo in one entry of a list can therefore silently drop a component. Check the generated build/site for the component folders you expect.

A related silent case is a branch pattern that matches no branch. Antora builds a site without that content and exits with code 0. The only hint is an informational message, which is hidden at the default log level:

[11:03:37.033] INFO (@antora/content-aggregator): No matching references found for content source entry (url: ./repo | branches: release_9*)

Run with --log-level=info to see it. The same applies to a tag pattern. When a freshly added content source does not show up in the site, this message is the first thing to look for. Related messages for sources that cannot be read at all are fatal and specific, for example Local content source does not exist: /work/pocket-units-docs/no-such-repo (url: ./no-such-repo).

Duplicate component versions

A component version is identified by the pair of name and version in antora.yml. When two references that are selected by the playbook declare the same pair, Antora merges them into one component version and then finds the same file twice. In the reproduction the main and release_1.0 branches both declared version: '2.0' because the version line was not updated after branching:

[11:03:21.581] FATAL (antora): Duplicate nav file: modules/guides/nav.adoc in 2.0@pocket-units
    1: /work/pocket-units-docs/repo/modules/guides/nav.adoc in /work/pocket-units-docs/repo (branch: main <worktree>)
    2: modules/guides/nav.adoc in /work/pocket-units-docs/repo/.git (branch: release_1.0)

The message lists both locations, which is the diagnosis: the two branches in lines 1 and 2 claim the same 2.0@pocket-units. The fix is to give each branch its own version, or to stop selecting one of them.

The version number alone is not the problem: the same version under a different component name is fine, because the pair must match. The rules for choosing versions are in Versioning and prerelease.

Unresolved xrefs and includes

Page conversion does not stop for a broken reference. Antora logs the problem, replaces the reference with a visible placeholder, writes the page and continues. A single page containing a missing page xref, a missing module, a missing component, a mistyped version, an unknown attribute, a missing image, a missing include and a missing tag produced:

[11:03:44.047] ERROR (asciidoctor): target of include not found: example$no-such-file.cs
    file: /work/pocket-units-docs/repo/modules/ROOT/pages/broken.adoc:13
[11:03:44.048] WARN (asciidoctor): tag 'nothing' not found in include file
    file: /work/pocket-units-docs/repo/modules/ROOT/examples/convert.cs
    stack:
      - file: /work/pocket-units-docs/repo/modules/ROOT/pages/broken.adoc:18
[11:03:44.049] ERROR (asciidoctor): target of include not found: partial$missing-partial.adoc
    file: /work/pocket-units-docs/repo/modules/ROOT/pages/broken.adoc:23
[11:03:44.050] ERROR (asciidoctor): target of xref not found: missing-page.adoc
[11:03:44.050] ERROR (asciidoctor): target of xref not found: guides:nothing.adoc
[11:03:44.051] ERROR (asciidoctor): target of xref not found: other-product::index.adoc
[11:03:44.051] ERROR (asciidoctor): target of xref not found: 1.5@getting-started.adoc
[11:03:44.051] WARN (asciidoctor): skipping reference to missing attribute: no-such-attribute
[11:03:44.051] ERROR (asciidoctor): target of image not found: missing.png

(The source: lines are omitted from this listing.) The build exited with code 0. In the generated HTML an unresolved xref is an anchor whose href starts with # and whose class is xref unresolved, for example href="#guides:nothing.adoc"; an unresolved include is replaced in the page by an Unresolved include directive line that names the page and the include; and a missing image is an img with the requested file name as src, which gives a broken picture. Searching the output is therefore another way to find problems: grep for xref unresolved and Unresolved include in build/site.

Things the messages do not tell you:

  • A missing anchor is not detected. xref:getting-started.adoc#no-such-anchor[text] produced no message and rendered a normal link to getting-started.html#no-such-anchor. Only the page is resolved.

  • A broken xref in a navigation file is reported with the nav file as the file:, for example target of xref not found: no-such.adoc for modules/ROOT/nav.adoc.

  • A start page that does not exist is only a warning: Start page specified for site not found: pocket-unit::index.adoc. The site is built without a working home redirect.

  • An xref is resolved relative to the page that contains it. An xref that omits the module works only inside the same module, and one that omits the component works only inside the same component version. A page in another component needs the component name, as in xref:other-product::index.adoc[]. The syntax is in Resource IDs and xrefs.

To fail the build on these messages, use --log-failure-level=error as shown above. Includes also depend on the family folder: example$ resolves to modules/<module>/examples, partial$ to modules/<module>/partials. See Partials, examples and attachments.

Stale UI cache

Antora downloads a remote UI bundle once and keeps the ZIP in its cache, keyed by the URL. A later build with the same URL reuses the cached copy, even when the file at the URL has changed. The symptom is a theme or template change that never reaches the site. The reproduction served a bundle from a local HTTP server, republished a changed bundle at the same URL and read the CSS marker that the changed bundle added to _/css/site.css:

Build ui.bundle.snapshot CSS marker in the output

First build, bundle version 1

not set

version 1

Same URL now serves version 2, no --fetch

not set

version 1

Same URL, with --fetch

not set

version 1

Same URL now serves version 2, no --fetch

true

version 1

Same URL, with --fetch

true

version 2

Only the combination of snapshot: true and a fetch refreshes the bundle. --fetch alone does not, because a bundle without snapshot is assumed to be immutable: that is correct for a versioned release URL and wrong for a URL such as a "latest" download link or a file on a branch. This site sets snapshot: true for its bundle, but its bundle URL is the local path ui-bundle.zip, which is read on every build, so the key has no effect there.

ui:
  bundle:
    url: https://example.org/downloads/ui-bundle.zip
    snapshot: true

Other ways to get a fresh bundle are to change the URL (a version in the file name is the cleanest), to point at a local unzipped directory while developing, or to delete the cached ZIP. The cache location depends on the platform: on Linux it is $XDG_CACHE_HOME/antora or $HOME/.cache/antora, on macOS $HOME/Library/Caches/antora and on Windows $APPDATA/antora/Caches. The scratch build above created a ui folder with the ZIP and a content folder with the cloned repositories inside the cache. --cache-dir, runtime.cache_dir or ANTORA_CACHE_DIR change the location, which is also how to get a clean cache in CI. The cache option and the Docker case are in CLI, Docker, cache and private repositories.

A wrong local bundle path is an explicit error. It stops the build with a message that shows both the resolved and the written path, and a relative path is resolved against the playbook file:

[11:07:36.291] FATAL (antora): Specified UI bundle does not exist: /work/ui/antora-ui-default/build/missing.zip (resolved from url: ../ui/antora-ui-default/build/missing.zip)

The same cache idea applies to remote content repositories: Antora clones them into the cache and only updates them with --fetch or runtime.fetch: true, so a branch pushed after the last fetch is not seen until then.

Jekyll-stripped folders

Antora writes the UI files into a folder named _ at the site root. The sample build produced a site root that contained exactly _ and pocket-units, and every page links to its styles through a path such as ../../_/css/site.css. Jekyll, which GitHub Pages runs when a site is published from a branch, ignores files and folders whose names start with an underscore. The published site then loads with no CSS, scripts, fonts or images from _, although the HTML pages are there. Typical symptoms are an unstyled page and 404 responses for _/css/site.css in the browser developer tools.

The cure is a file named .nojekyll at the root of the published folder, or publishing with the actions/deploy-pages flow, which does not run Jekyll. A supplemental file entry alone is not enough: in the reproduction a supplemental_files item with only path: .nojekyll (with or without empty contents) produced no file in the output, and listing it under static_files in ui.yml did. This is the configuration that produced .nojekyll in build/site:

site:
  title: Pocket Units Docs
content:
  sources:
  - url: ./repo
    branches: HEAD
ui:
  bundle:
    url: ../ui/antora-ui-default/build/ui-bundle.zip
  supplemental_files:
  - path: ui.yml
    contents: |
      static_files:
      - .nojekyll
  - path: .nojekyll

The ui.yml entry replaces the bundle’s own ui.yml, so if the bundle declares other static files, list them too. The listing of the site root after the build was .nojekyll, _ and pocket-units. Jekyll itself was not run in the scratch environment, so the effect on GitHub Pages is described from the official publishing guide and not reproduced here. The complete branch-based publishing flow is in Publishing.

Stale output and other quick checks

  • Antora does not empty the output folder before writing. In the scratch builds, a build that selected no content still left the folders of the earlier build in build/site, so pages of a branch that is no longer selected can linger. Pass --clean (remove the destination path before publishing) or delete the folder in CI.

  • A build that finishes but has the wrong pages is usually a content source problem. Run once with --log-level=info and compare the selected references with the folders in the output.

  • An error that names a file you cannot find is usually in the commit that is being read, not in the worktree. For a local repository only the checked-out branch is read from the worktree; other branches are read from Git, so uncommitted changes to them are not seen.

  • To rule out the cache, run once with a new empty --cache-dir.