Intersphinx

The [intersphinx] section configures cross-project references against external Sphinx inventories (objects.inv files), mirroring Sphinx’s intersphinx extension. Each configured project makes its documented objects (labels, terms, functions, and so on) resolvable from your own project.

Minimal example

[intersphinx]
cache_days = 5                    # 0 = always refresh, negative = never expire
timeout = 30.0                    # seconds per HTTP request
disabled_reftypes = ["std:doc"]   # "*" and "domain:*" forms supported
offline = false                   # never fetch; serve cache or fail soft

[intersphinx.projects.python]
url = "https://docs.python.org/3"
# optional; defaults to ["<url>/objects.inv"]; entries are URLs or paths
# relative to the ubproject.toml directory; tried in order (Sphinx parity)
inventories = ["https://docs.python.org/3/objects.inv"]

[intersphinx.projects.sphinx]
url = "https://www.sphinx-doc.org/en/master"

How references reach the inventories

Mirroring Sphinx, a reference consults the configured inventories in three ways:

  • Automatic fallback: a plain :ref:, :term: or :doc: whose target is not found in your own project is looked up across all configured inventories, unless its type is listed in disabled_reftypes (:doc: is excluded by default, exactly as in Sphinx).

  • Inventory-qualified targets: a target written project:name (for example :ref:`python:comparisons`) looks up name in that one project’s inventory — and deliberately ignores disabled_reftypes, because writing the prefix is itself the opt-in.

  • Explicit roles: the :external: role family (:external:ref:`…`, :external+python:py:func:`…`) always targets the inventories and also ignores the list.

The automatic fallback means a typo in a :ref: target can, in principle, silently match an identically-named object in an external project — the same exposure a Sphinx build has. A dangling reference that matches nowhere warns (std.ref / std.doc / std.term), so only an accidental name collision resolves silently. To opt out of the automatic behaviour entirely, set disabled_reftypes = ["*"] — qualified targets and explicit roles keep working, so every external link is then a visible, deliberate one.

Options

cache_days

Type: integer (default: 5)

Inventory cache lifetime in days, matching Sphinx’s intersphinx_cache_limit semantics: a positive value expires a cached inventory after that many days (after which it is revalidated with a conditional request), 0 always refreshes, and a negative value never expires the cache.

timeout

Type: number (default: 30.0)

Timeout, in seconds, for each inventory HTTP request.

disabled_reftypes

Type: array of strings (default: ["std:doc"])

Reference types excluded from the automatic (un-qualified) fallback, such as "std:doc". The "*" (all) and "domain:*" (whole-domain) wildcard forms are supported. Inventory-qualified targets and explicit :external: roles ignore this list.

Entries name the inventory object type: "std:label" for :ref:, "std:term" for :term:, and "std:doc" for :doc:. Sphinx’s intersphinx_disabled_reftypes names :ref: as "std:ref"; ubcode accepts that spelling as an alias of "std:label", so a list copied from conf.py behaves identically.

offline

Type: boolean (default: false)

When true, never perform network fetches: inventories are served from the cache (stale is acceptable) or the reference fails soft with a diagnostic (references never fail the build). cache_days then only affects labelling — an expired-but-present cached inventory is served as stale (an informational diagnostic) rather than as a silent cache hit.

resolve_self

Type: string (default: "")

The name under which this project refers to itself, mirroring Sphinx’s intersphinx_resolve_self. A reference whose inventory qualifier equals this name resolves locally (the qualifier is stripped and the project’s own targets are consulted) instead of against an external inventory — for example, with resolve_self = "myproj", both a :ref: targeting myproj:some-label and the explicit :external+myproj:ref: role form resolve locally. Empty (the default) disables the behaviour. The self name takes precedence over a configured project of the same name, so avoid reusing an entry from [intersphinx.projects] here — that project would become unreachable via qualified references.

projects

Type: table (default: empty)

A table of external projects keyed by name ([intersphinx.projects.<name>]). The name is the prefix used by inventory-qualified lookups. Each project accepts:

url

Type: string (required)

Base URL of the external project’s documentation. Generated links are relative to this URL, and it must be unique across all configured projects.

inventories

Type: array of strings (default: derived from url)

Ordered inventory locations to try. Each entry is either a URL (it contains "://") or a filesystem path relative to the ubproject.toml directory. When omitted, the single location "<url>/objects.inv" is used.

Checking what the configuration resolves

To see exactly which references the configured inventories make available, list them with the CLI:

ubc intersphinx list                      # every configured project
ubc intersphinx list python               # only [intersphinx.projects.python]
ubc intersphinx list -d py -o function    # filter by domain / object type
ubc intersphinx list -f json              # machine-readable output

The command resolves the nearest ubproject.toml (use -p/--project to point it elsewhere), loads each project’s inventory through the shared inventory cache — honouring cache_days, timeout, and offline — and prints one block per project without indexing the project. A project whose inventory cannot be loaded is reported on standard error and skipped, the remaining projects are still listed, and the command exits non-zero.

To inspect a single objects.inv file or URL without any project configuration, use ubc intersphinx inspect instead.

To search the loaded inventories together with the project’s own labels, glossary terms, documents and filed index entries — ranked, with the exact :external: role to paste — use ubc query search (see Finding what you can reference):

ubc query search --scope python array     # only [intersphinx.projects.python]
ubc query search --scope external array   # any loaded inventory
ubc query search --kind py:function       # by object type

Unlike ubc intersphinx list, it indexes the project first, so it can answer for local and external targets in one ranked result, and a project whose inventory failed to load is reported on standard error (and in the inventories array of -f json) so an absent symbol is not mistaken for an absent inventory.

Comparing two inventories

ubc intersphinx diff compares two inventories and reports what changed:

ubc intersphinx diff old/objects.inv new/objects.inv
ubc intersphinx diff -d std a.inv b.inv        # only the std domain
ubc intersphinx diff -f json a.inv b.inv       # machine-readable output
ubc intersphinx diff https://example.com/docs/ ./site/objects.inv

Each argument is a local path or an HTTP(S) URL, resolved exactly as inspect resolves its own — URLs go through the shared inventory cache, so --refresh and --offline mean the same thing here. The -d / -o / -n filters apply to both inventories before comparing, so a scoped diff answers “are these equal within this scope?”. A filter that matches nothing on either side compares two empty sets, which are equal — the command reports 0 identical, and exits 0 when the two files’ headers also agree. A header difference is still reported through any filter, because the header describes the file rather than the filtered view. When a diff is used as a CI gate, check the filter spelling: against two files that share a header, a mistyped -d greens the gate rather than failing it.

The report names the changed column, which is what distinguishes a moved anchor from a re-worded title:

header
  ~ project              ubCode -> (blank)
std:label
  ~ intro dispname Getting started -> Getting Started
std:term
  ~ RST uri glossary.html#term-RST -> glossary.html#term-rst
summary: 0 removed, 0 added, 2 changed (1 uri, 1 dispname, 0 priority); 301 identical

Two inventories are compared the way every reader reads them — keyed on (domain:type, name), which is how Sphinx’s own loader builds its map. Two things therefore do not count as differences. Row order is one: producers sort differently (Sphinx orders by name within a domain, interleaving documents, labels and terms), and no consumer can observe the difference, so two inventories holding the same rows in a different order compare as identical. The $ anchor abbreviation is the other: it is expanded on load, so index.html#$ and index.html#my-label are the same URI for the entry my-label. A - display name — and one that simply repeats the entry name — are likewise one state.

Exit codes make the command usable as a CI gate:

Code

Meaning

0

The two inventories are identical (header included).

1

Differences were found. The report is on standard output.

2

No comparison was delivered: an inventory could not be read or parsed, or the report itself could not be written (a closed pipe, a full disk).

Pass --exit-zero to report differences without failing the build; it changes the exit status only, never the report. It suppresses 1 and deliberately not 2 — a run that could not produce a report has not shown that the inventories agree.

Network-restricted CI

Because inventory fetches happen over the network, CI environments without outbound access should either set offline = true with committed local inventories files (the Doxygen-tagfile mode), or pre-warm the inventory cache before the run. Fetch failures are reported as warnings and never fail ubc check, so cross-project references simply fall back to placeholders if no inventory is available.