Antora Xref Troubleshooting Guide

The Problem

Antora builds fail with errors like:

target of xref not found: 01/index.adoc
target of xref not found: infra-ops::runbooks/backup-strategy.adoc
target of xref not found: template.adoc

These errors occur because Antora xrefs do NOT work like file system paths.

Root Cause Analysis

Issue 1: Cross-Component Xrefs in Spoke Repos

Symptom:

target of xref not found: infra-ops::runbooks/backup-strategy.adoc

Root Cause:

Cross-component xrefs (xref:component::path.adoc[]) require ALL components to be present during build. They ONLY resolve when building through the aggregator (domus-docs).

When building a spoke repo standalone (e.g., cd domus-captures && make), the aggregator isn’t involved. Antora only sees ONE component, so references to other components fail.

Why This Happens:

# Aggregator build (works)
domus-docs/
├── antora-playbook-local.yml  # Lists ALL components
└── builds with: npx antora antora-playbook-local.yml
    → All 12 components loaded
    → xref:infra-ops::... resolves ✓

# Standalone build (fails)
domus-captures/
├── antora-playbook-local.yml  # Lists ONLY captures
└── builds with: npx antora antora-playbook-local.yml
    → Only captures component loaded
    → xref:infra-ops::... has nothing to resolve to ✗

Issue 2: Relative Path Xrefs

Symptom:

target of xref not found: ../06/index.adoc
target of xref not found: template.adoc

Root Cause:

Antora xref paths are relative to the pages/ directory, NOT to the current file’s location.

What You Write What You Expect What Antora Does

xref:../06/index.adoc[]

Resolve relative to current file

Looks for pages/../06/index.adoc (invalid)

xref:template.adoc[]

Same directory as current file

Looks for pages/template.adoc (wrong location)

xref:2025/06/index.adoc[]

-

Looks for pages/2025/06/index.adoc

Why This Happens:

Antora creates a virtual file system from all pages. The xref macro resolves paths from the module’s pages/ root, not from the current file’s directory.

# File: pages/2025/05/index.adoc
# Trying to link to: pages/2025/06/index.adoc

# WRONG - file-system thinking
xref:../06/index.adoc[]  # Antora sees: pages/../06/index.adoc ✗

# CORRECT - Antora path from pages/ root
xref:2025/06/index.adoc[]  # Antora sees: pages/2025/06/index.adoc ✓

Issue 3: Same-Directory Xrefs

Symptom:

# From pages/runbooks/index.adoc
target of xref not found: template.adoc

Root Cause:

Even for files in the same directory, you need the full path from pages/:

# WRONG
xref:template.adoc[]  # Looks for pages/template.adoc ✗

# CORRECT
xref:runbooks/template.adoc[]  # Looks for pages/runbooks/template.adoc ✓

Prevention Strategy

Rule 1: Never Use Cross-Component Xrefs in Spoke Repos

Location Allowed Xrefs

Spoke repos (domus-captures, domus-infra-ops, etc.)

Only same-component xrefs + plain text references

Hub repo (domus-docs/docs/)

Cross-component xrefs allowed

Instead of:

* xref:infra-ops::runbooks/backup-strategy.adoc[Backup Strategy]

Write:

* Backup Strategy (infra-ops)

Rule 2: Always Use Absolute Paths from pages/

Instead of:

xref:../06/index.adoc[June]
xref:template.adoc[Template]

Write:

xref:2025/06/index.adoc[June]
xref:runbooks/template.adoc[Template]

Rule 3: Pre-Commit Checks

Add these checks before committing:

# Check for cross-component xrefs (forbidden in spoke repos)
grep -rn 'xref:[a-z-]*::' docs/
# Should return empty

# Check for relative path xrefs (always forbidden)
grep -rn 'xref:\.\.' docs/
# Should return empty

Rule 4: Test Builds Before Pushing

# In any spoke repo
make  # or: npx antora antora-playbook-local.yml

# Check for errors
make 2>&1 | grep '"level":"error"'
# Should return empty

Quick Reference

Valid Xref Patterns

// Same module, explicit path from pages/
xref:2025/06/index.adoc[June 2025]
xref:runbooks/template.adoc[Template]
xref:index.adoc[Home]

// Cross-component (ONLY in domus-docs/docs/)
xref:infra-ops::runbooks/vault-pki.adoc[Vault PKI]

Invalid Xref Patterns

// Relative paths (NEVER)
xref:../06/index.adoc[June]
xref:./template.adoc[Template]

// Same-directory shorthand (NEVER)
xref:template.adoc[Template]

// Cross-component in spoke repos (NEVER)
xref:infra-ops::index.adoc[Infra Ops]

Fixing Existing Errors

Step 1: Identify the Error Type

# Run the build
make 2>&1 | grep '"level":"error"' | head -20

Step 2: Categorize

Error Pattern Fix

xref not found: component::path

Convert to plain text: "Label (component)"

xref not found: ../path

Use absolute path from pages/: xref:full/path/file.adoc[]

xref not found: file.adoc

Add directory: xref:directory/file.adoc[]

Step 3: Bulk Fix

# Find all cross-component xrefs
grep -rn 'xref:[a-z-]*::' docs/

# Find all relative xrefs
grep -rn 'xref:\.\.' docs/

# Use sed or manual edit to fix

Architecture Summary

domus-docs (AGGREGATOR/HUB)
├── Can use cross-component xrefs ✓
├── Builds ALL components together
├── docs/modules/ROOT/pages/  ← Cross-component xrefs OK here
└── BUT: index.adoc should have ZERO cross-component deps!
         (Spoke playbooks pull in hub but not all components)

domus-* (SPOKE REPOS)
├── Cannot use cross-component xrefs ✗
├── Build standalone OR through aggregator
├── Must use plain text for cross-component references
└── docs/.../modules/ROOT/pages/  ← Only same-component xrefs
The hub landing page (index.adoc) is special. Spoke playbooks (like domus-infra-ops) include domus-docs as a source. If the hub’s index.adoc has cross-component xrefs (e.g., to captures), they will fail during spoke builds because captures isn’t in that playbook. Keep index.adoc free of cross-component dependencies.

Lessons Learned (2026-02-13 Incident)

What Happened

  1. Created 2025 historical documentation with month subdirectories

  2. Used intuitive file-system relative paths (../06/index.adoc)

  3. Used cross-component xrefs to link to infra-ops, linux-ops, etc.

  4. Builds failed with 30+ xref errors

  5. Spent significant time debugging because the files existed

Why It Was Confusing

  • Files existed on disk - ls showed them, git tracked them

  • Paths looked correct - ../06/index.adoc makes sense for file systems

  • Worked conceptually - the mental model was "link to this other file"

The Mental Model Shift

File System Thinking Antora Thinking

Paths are relative to current file

Paths are relative to pages/ directory

../ means parent directory

../ is invalid in xrefs

Same directory = just filename

Same directory = full path from pages/

All files are accessible

Only components in playbook are visible

The Fix Process

  1. Identify: make 2>&1 \| grep error

  2. Categorize: Cross-component vs relative path vs same-directory

  3. Fix cross-component: Convert to plain text "Label (component)"

  4. Fix relative paths: Use absolute paths from pages/

  5. Verify: Rebuild and confirm zero errors

  6. Document: Update CLAUDE.md with prevention rules

Time Cost

  • Initial debugging: ~30 minutes of confusion

  • Systematic fix: ~15 minutes

  • Documentation: ~20 minutes

  • Prevention value: Hours saved on future occurrences

Prevention Checklist

Before committing documentation changes:

  • Run grep -rn 'xref:[a-z-]*::' docs/ - should be empty (spoke repos)

  • Run grep -rn 'xref:\.\.' docs/ - should be empty (all repos)

  • Run make and verify zero errors

  • If adding new pages, use full paths from pages/ directory

Addendum: 2026-02-19 Incident

New Error Types Discovered

Issue 4: Cross-Component Images

Symptom:

target of image not found: home::diagrams/work-tracker-2026-02-compact.svg

Root Cause:

Unlike xrefs, images CANNOT use cross-component syntax (component::path). The image:: macro only supports:

  • Local paths (relative to module’s images/ directory)

  • Absolute URLs (…​;)

Syntax Works?

image::diagram.svg[]

Yes (local)

image::diagrams/flow.svg[]

Yes (local subdirectory)

image::https://example.com/img.png[]

Yes (URL)

image::home::diagrams/file.svg[]

NO (cross-component not supported)

image::infra-ops::diagrams/arch.svg[]

NO (cross-component not supported)

Fix:

Cross-component images have NO workaround. Options:

  1. Copy the image to the local component’s images/ directory

  2. Use URL if image is hosted (e.g., image::https://docs.example.com/images/file.svg[])

  3. Remove the reference and link to the page instead (if in hub only):

    // WRONG - never works
    image::home::diagrams/work-tracker.svg[]
    
    // OPTION - external URL (if hosted)
    image::https://docs.domusdigitalis.dev/home/2026/_images/diagrams/work-tracker.svg[]
    
    // OPTION - remove entirely and use plain text
    See xref:captures::2026/02/WRKLOG-2026-02-19.adoc[Work Tracker] for current status.
Cross-component xrefs in domus-docs ALSO fail when building from spoke playbooks. If you run make from domus-infra-ops, its playbook pulls in domus-docs but NOT all other components (like captures). The hub’s cross-component xrefs then fail. Solution: The hub landing page should have ZERO cross-component dependencies. Only use cross-component xrefs in hub pages that are never built through spoke playbooks.

Issue 5: Same-Directory Xrefs in Nested Paths

Symptom:

# File: pages/cli/ise/ers/dacls.adoc
target of xref not found: authz-profiles.adoc
target of xref not found: network-devices.adoc

Root Cause:

Files exist in the same directory (cli/ise/ers/), but Antora resolves ALL xrefs from the module root (pages/), not from the current file’s location.

# Current file: pages/cli/ise/ers/dacls.adoc
# Target file:  pages/cli/ise/ers/authz-profiles.adoc

# WRONG - assumes same-directory resolution
xref:authz-profiles.adoc[]  # Looks for pages/authz-profiles.adoc ✗

# CORRECT - full path from module root
xref:cli/ise/ers/authz-profiles.adoc[]  # Looks for pages/cli/ise/ers/authz-profiles.adoc ✓

Rule: ALWAYS use full paths from pages/, regardless of current file location.

Issue 6: List Continuation with Code Blocks

Symptom:

list item index: expected 1, got 3

Root Cause:

Numbered lists with code blocks between items break list continuation:

// WRONG - list breaks after code block
1. First step
2. Second step:

[source,bash]

some command

3. Third step    <-- Antora sees this as "1" not "3"

Fix:

Use AsciiDoc auto-numbering (.) with continuation marker (+):

// CORRECT - proper list continuation
. First step

. Second step:
+
[source,bash]
----
some command
----

. Third step    <-- Correctly continues as item 3

Updated Prevention Checklist

Before committing documentation changes:

  • Run grep -rn 'xref:[a-z-]*::' docs/ - empty in spoke repos

  • Run grep -rn 'xref:\.\.' docs/ - empty in all repos

  • Run grep -rn 'image::[a-z-]*::' docs/ - empty in all repos (NEW)

  • Check numbered lists with code blocks use . and + continuation

  • Run make and verify zero errors/warnings

Pre-Commit Grep Commands

# Cross-component xrefs (forbidden in spoke repos)
grep -rn 'xref:[a-z-]*::' docs/

# Relative path xrefs (always forbidden)
grep -rn 'xref:\.\.' docs/

# Cross-component images (always forbidden)
grep -rn 'image::[a-z-]*::' docs/

# Hardcoded list numbers that might break
grep -rn '^[0-9]\+\. ' docs/