Linting

The [lint] section controls which diagnostics ubCode reports. Use it to suppress noisy warnings globally, override suppression for specific codes in the LSP, or target ignores to particular files or message patterns.

Minimal example

[lint]
ignore = ["block.title_line"]

[lint.per-file-ignores]
"tests/**/*.rst" = ["needs.dead_link"]

Core options

ignore

Type: array (default: [])

List of linting error codes to ignore globally across the project.

lsp_select

Type: array (default: [])

List of linting codes to specifically enable in Language Server Protocol (LSP) file diagnostics. This setting overrides the ignore list, allowing you to see specific errors in your editor even if they’re globally ignored.

[lint]
# List of linting codes to ignore
ignore = ["block.title_line"]
# A list of linting codes to select in LSP file diagnostics
# This overrides the ignore list.
lsp_select = ["block.title_line"]

Code matching and .* wildcards

Everywhere a list of diagnostic codes is configured — ignore, lsp_select, the code lists of per-file-ignores, and the codes scope of message-ignores — an entry matches a diagnostic in one of two ways:

  • Exactly: "needs.dead_link" matches only the needs.dead_link code.

  • As a family wildcard: a trailing .* matches a whole dot-namespaced family — "toctree.*" matches every toctree.<subcode> diagnostic, and also the bare family code itself (so "config.*" covers both the fine-grained config.<subcode> diagnostics and the plain config ones).

[lint]
# Suppress the whole std family — the dangling references
# (std.ref, std.doc, std.term) and the project-wide duplicates
# (std.duplicate_target, std.duplicate_label, std.duplicate_term)
# — plus one exact code.
ignore = ["std.*", "block.title_line"]

No other glob positions are supported: entries like "*.ref", "con*", or a bare "*" are treated as plain literals and match no real code.

A bare family name is not an umbrella: ignore = ["config"] matches only a diagnostic whose code is exactly config — it does not reach config.unknown_extension and its siblings. To suppress a whole family, use the .* wildcard.

Configuration diagnostics (config and config.*)

Problems found while resolving ubproject.toml itself are reported against the configuration file. The checks with a fine-grained code are individually suppressible:

  • config.unknown_extension — a [parse].extensions entry that looks like a typo of a known port name.

  • config.unknown_extension_option — a key other than active inside a [parse.extensions] entry table. The entry still switches its port; the key itself is ignored.

  • config.extension_always_on — a [parse].extensions entry switching off something built into ubCode (sphinx_needs), which stays active.

  • config.extension_unsupported — a [parse].extensions entry switching on an extension ubCode does not support (the sphinx.ext.autodoc family, sphinx.ext.graphviz); it runs only under sphinx-build.

  • config.extension_unimplemented — a [parse.parsers.<name>].extensions entry naming a MyST extension ubCode has not implemented yet; the entry is ignored and the syntax renders as written.

  • config.unknown_md_extension — a [parse.parsers.<name>].extensions entry naming something MyST-Parser does not recognise either. Deliberately a separate code from config.unknown_extension above, which is the Sphinx-port typo check, so one ignore entry cannot silence both.

  • config.extension_renamed — a [parse].extensions entry spelling an extension by its old name (sphinxcontrib.test_reports), which is read as the current one (sphinx_test_reports). Information, not a warning: the entry works. A separate code from config.extension_deprecated below, which is the MyST vocabulary’s.

  • config.extension_deprecated — a [parse.parsers.<name>].extensions entry naming a MyST extension whose upstream has deprecated it (attrs_image, superseded by attrs_inline). The extension still works; the message names the replacement. Reported once per project, at configuration load, as sphinx-build reports it once per build.

  • config.invalid_extlink_template — a [parse.extlinks] entry with an invalid %s template.

  • config.extlink_shadows_builtin — a [parse.extlinks] role name that would shadow a built-in role.

  • config.extlinks_data_inert — a populated [parse.extlinks] table whose port is switched off in [parse].extensions, so none of its roles is registered. Most often an inherited = false meeting a child’s own table.

  • config.unknown_default_role — a [parse].default_role naming an unknown role.

  • config.legacy_anchor_redirects_own_style — a [build.html] legacy_anchor_redirects entry naming the project’s own [parse] id_style, which generates nothing.

  • config.conflicting_content_model — a [parse.extend_directives] entry named like a built-in directive with a different body mode; the entry is not used. (A [[needs.types]] entry named like a built-in takes the name instead — the next code.)

  • config.conflicting_directive_registration — a directive name claimed by two sources, reported once for each entry the rule overrides or leaves unused. A directive name has one owner, by rank: a switched-on sphinx-test-reports directive over a [[needs.types]] entry; a [[needs.types]] entry over a built-in directive and over a [parse.extend_directives] entry; a [parse.extend_directives] entry for a name no built-in holds; and, last, a switched-off sphinx-test-reports directive, which keeps only a name nothing else claims. Two entries of one kind whose names differ only in case are one name, and are reported too. The message states which entry is used — and, for a need type named like a domain directive ubCode knows (the std domain’s, such as envvar and option, and class), that a Sphinx build keeps the domain’s directive. A name ubCode does not model (function, module, …) is simply the type’s here, with no finding, where Sphinx’s py domain keeps it.

  • config.string_link_incomplete — a [needs.string_links] rule missing one of its four required keys.

  • config.string_link_regex_invalid — a string-link regex that does not compile (including one using look-ahead, look-behind or a backreference, which this engine does not support).

  • config.string_link_template_invalid — a string-link link_url or link_name that is not a valid template.

  • config.string_link_unknown_field — a string-link options entry naming a field the project never declared.

  • config.field_shadowed_by_identity — a project declares a field or link named content, on any of [needs.fields], [needs.links], extra_options or extra_links. content is the need’s own body, which no declaration can shadow. The declaration is kept, so nothing that renders today stops rendering — the option can still be set on a directive, and a link’s targets are still validated — but filters, tables, variant conditions, report templates and needs.json all answer the body for that name, so the declared value is invisible to every one of them. Rename the field or link; ignore silences the warning while you migrate. (id and type, the other two un-shadowable names, are refused outright and report under the plain config code.)

  • config.derive_invalid — a derived field’s derive rule that cannot be read: a derive that is not a table, a missing or unknown kind, an unknown role or one the kind does not take, a missing role, a role whose value has the wrong type, a value the field’s schema cannot hold, a filter that is not portable, an over naming no link type, two rules reading each other’s values, or a rule on a core field; a default or predicates entry beside a rule; copy = true on a derived link type, and a link type copied (copy = true) into a derived links. One finding per rule, at its key, naming the kind and the role. The field stays derived and holds its empty value (a rule on a core field is ignored instead), and a key beside a readable rule is ignored.

  • config.derive_sphinx_minimum — the project declares a derived field, which sphinx-needs reads only from the release the notice names; an earlier sphinx-needs ignores derive silently. Reported once per project, at the rule of the first derived field by name (or link type, when no field has one). Graded info, not warning, for the reason config.variant_sources_sphinx_unsupported is: ubCode cannot see which sphinx-needs a project builds with, and a warning would fail ubc check on a correctly configured project. ignore silences it once you have checked.

  • config.schema_unknown_key — a key in a schemas.json rule that neither ubCode nor sphinx-needs accepts, such as selct for select, propertes for properties, or a JSON-Schema keyword the rule grammar has no place for. Such a key used to be ignored, which silently changed what the rule meant: a misspelled select left the rule with no filter, so it applied to every need, and a misspelled key inside validate.local produced a rule that validated nothing at all. The message names the offending key, the rule and the keys accepted at that position. No rule from the file is applied while any such key remains, which is what sphinx-needs does too — it refuses to build at all.

  • config.schema_missing_validate — a schemas.json rule with no validate member. validate is what says what to check, so such a rule enforces nothing at all, which used to happen silently. sphinx-needs requires the member outright. A rule whose validate is present but empty is not reported — it asks for nothing and gets nothing, which is a rule doing what it says. No rule from the file is applied while the member is absent.

  • config.schema_invalid_value — a key in a schemas.json rule that both engines accept, whose value sphinx-needs constrains and ubCode found wrong: a type that is not "object" at a select, a validate.local or an allOf element, or not "array" on a network link block; a required that is not an array of strings; an unevaluatedProperties that is not a boolean; or a network link block that cannot be read at all (not an object, a contains or items that is not an object, a minContains or maxContains that is not an integer — any integer is accepted, whatever its sign or size, because that is all sphinx-needs requires, and a negative bound is applied when the rule runs rather than refused when it loads). The message names the key, the rule, the value found and the value expected. All four type positions used to be accepted silently. In a select or a validate.local ubCode ignores that value, so the check is for parity and clarity; inside an allOf element it is compiled verbatim, which is the costly case — "type": "string" makes the element match no need, so the rule enforces nothing. required and unevaluatedProperties used to be warnings whose constraint was then dropped, so the rule ran without it, and an unreadable network link block was dropped whole in silence. No rule from the file is applied while any such value remains; measured at sphinx-needs 8.4.0, sphinx-build aborts on each of the values listed here, and this code fires only where it does. Fix the value rather than ignoring the code: ignore silences the report but the file stays refused, so no rule from it is applied and ubc check then exits 0 on a project whose schema validation is switched off.

  • config.schema_sphinx_needs_incompatible — a schemas.json construct ubCode honours but sphinx-needs rejects, so sphinx-build fails on a file ubCode is happy with. In practice this is an extra keyword inside an allOf element, such as a not. The rule keeps working; only ignore it if the project never builds with sphinx-needs.

  • config.codelinks_removed_key — a codelinks project’s analyse table still sets get_rst = false, the removed marked-RST key (see analyse). It asks for nothing; delete the line, or ignore the code while the file is shared with tools that still read it. Any other get_rst value, and an [analyse.marked_rst] table, are refused and that codelinks project is skipped; the refusals carry no code of their own, so a listed code cannot single them out — but the config wildcard hides every configuration diagnostic, the fatal ones included (pre-existing, #3961).

  • config.mount_invalid — a [[source.mounts]] entry whose mount_at, attach_to or entry_doc is not a relative document name, or whose files list is empty. The mount is dropped.

  • config.mount_unknown_key — a key on a mount entry that ubCode does not model. The key is ignored and the rest of the mount is honoured, so one ubproject.toml can be shared with sphinx-mounts.

  • config.mount_at_root — a mount with no mount_at, so it is mounted at the document root where any file it contributes can take the name of a host document.

  • config.mount_dead_option — a mount key its own mode never reads: include or exclude on a files mount (which has no walk to filter), or attach_each on a dir mount, without attach_to, or beside a non-default entry_doc.

  • config.mount_missing — a declared mount root that is not on disk. The mount contributes nothing this run and the pages it contributed on an earlier run are kept rather than reported as deleted.

  • config.mount_attach — a mount whose attach_to request could not be honoured: the host document does not exist, toctree_index is past the end of it, or the mount produced no document to attach. Not reported for a mount this variant gates off, which has nothing to attach by design.

  • config.mount_gated — a mount whose if gates it off for this variant, so it contributes no documents. Graded info, not warning: gating is what you asked for, and grading it a warning would fail ubc check on every build of every variant that gates anything. It is reported at all — and reported even when nothing in the project references the bundle — because a gated mount is otherwise a silent absence of a whole tree that may live in another repository.

  • config.mount_gated_contested — a gated mount that is not credited with the documents it removed, because at least one of them carries a name the build already uses. Toctree entries naming its pages then report toctree.nonexisting_document rather than the gate. Also graded info: it explains a warning that fires elsewhere rather than being one.

  • config.mount_invalid_condition — a mount whose if is not a valid condition. The mount is gated off, in every variant, until the condition is rewritten.

  • config.mount_condition_unevaluable — a mount whose if could not be evaluated against the variant data, an unknown var.* key being the common cause. The mount is gated off.

  • config.mount_excludes_root — a mount this variant gates off is the only root supplying [project] root_doc. This one refuses the whole configuration rather than reporting, so ignore does not reach it: nothing resolves. The same posture as config.variant_source_excludes_root below, and for the same reason.

  • config.variant_sources_sphinx_unsupported — a project declares a variant-gating key: [[source.variant_sources]] rules, or if on a [[source.mounts]] entry. sphinx-build reads either one only through sphinx-mounts at a release that supports it: without that, the two tools see different document sets. Graded info, not warning: it fires on every project that declares either key — ubCode cannot see what is installed on the Sphinx side, so it cannot tell you which case you are in — and grading it a warning would fail ubc check on a correctly configured project. ignore silences it once you have read it.

  • config.variant_source_unknown_key — a key on a variant_sources rule that ubCode does not model. Ignored, and the rest of the rule honoured, for the same shared-ubproject.toml reason as config.mount_unknown_key.

  • config.variant_source_invalid — a variant_sources rule with an empty files list. The rule is dropped, which is safe: it selected nothing.

  • config.variant_source_invalid_condition — a rule whose if is not a valid rule condition, or cannot be evaluated against the current variant data (an unknown var.* key is the usual cause). The rule’s files are excluded — warn-and-exclude, the same contract the if directive has. Silencing this code does not put the files back; it only hides the reason they are gone.

  • config.variant_data_location — both the shared [variants] table and the legacy [needs] variant_data / variant_data_file keys set the variant data. [variants] is read whole, and each [needs] key it ignores is reported at that key; the ignored data file is not opened. Remove the [needs] keys — here, or in a configuration this one extends. While a shared base is still on [needs], migrate it, or add this code to the child’s ignore (which replaces an inherited list — repeat the base’s entries).

  • config.variants_unknown_key — a key under [variants] other than data and data_file. Ignored, and the rest of the table read, so one ubproject.toml can serve tool versions that read more keys.

  • config.variant_data_invalid — the variant data cannot be used as written: an empty data_file (or variant_data_file), a data file that is missing, unreadable, not JSON, or not one object of variant values (a JSON null, an array of arrays or of objects, a mixed-type array), or a mixed-type array or TOML date-time in the inline table. Reported at the key it concerns (variants.data / variants.data_file, or needs.variant_data / needs.variant_data_file on a project still on [needs]). It is a refusal, graded a warning, so ubc check and ubc build html exit non-zero: fix the data rather than ignore the code. These findings used to report under the plain config code, so a bare "config" entry in ignore (or in a per-file-ignores or message-ignores code list) silenced them; it no longer does. To keep them silent, name this code (or "config.*").

  • config.test_reports_unknown_key — a key under [test_reports], [test_reports.build] or [test_reports.build.needs] that ubCode does not read: one finding per table, naming every such key and the supported ones. Ignored, and the rest of the table read, so a ubproject.toml written for a newer sphinx-test-reports still loads.

  • config.test_reports_type_declared — the project declares a [[needs.types]] entry under a name [test_reports] would add (testcase and its siblings) (or one of the default types has the name, when the project declares none), and its title, prefix, color or style differs from the section’s. The declared type is used unchanged; the message names both values.

  • config.test_reports_link_target_undeclared — a property_link_types (or build.needs.link_properties) target that is neither links nor a declared link type. Its values are not linked; declare it under [needs.links].

  • config.test_reports_link_properties_differ — both property_link_types and its deprecated alias build.needs.link_properties are set, and they differ. property_link_types is used.

  • config.test_reports_rename_unsupported — a file, suite or case entry renames its directive. Not supported yet: the directive keeps its default name.

  • config.test_reports_url_pattern — [test_reports.build.needs] url_pattern is not a usable source-link pattern (an unknown or positional placeholder, unbalanced braces). The converter refuses it; ubCode reports it, since the build itself makes no source link.

  • config.test_reports_field_type_mismatch — the project declares one of the fields [test_reports] adds with a type the test-report values may not fit, or uses its name for a link type (then the field is not added). The declaration is kept. Not reported for a number (time) or a number or an integer (the source-line field, case_line): their values are numeric strings, which convert.

  • config.test_reports_switched_off — "sphinx_test_reports" = false (or { active = false }) in [parse.extensions]: the extension’s need types and fields are not added and its directives render as placeholders, unless a need type of the same name owns them. Information, reported once, with or without a [test_reports] section.

  • config.test_reports_extra_option_reserved — an extra_options name that cannot be a field: an empty name, a name [needs.fields] itself refuses or warns about (id, type, content, var), a link type, or a core field of another type such as tags. The name is ignored. (A name the section adds itself, such as failed, is simply skipped.)

Two variant_sources problems are hard configuration errors rather than lint findings, so ignore does not reach them — nothing resolves, so nothing is linted:

  • config.variant_source_invalid_glob — a rule glob using {a,b} alternation or climbing out of the project with ... Refused rather than skipped: skipping the rule would leave every file it names in the build, including the files its other patterns name.

  • config.variant_source_excludes_root — a rule that is false for the current variant and would remove [project] root_doc. The message names the pattern to narrow.

All other configuration warnings (for example an unknown needs.global_options key) report under the plain config code.

[lint]
# Suppress just the typo check…
ignore = ["config.unknown_extension"]
# …or the whole configuration channel:
# ignore = ["config.*"]

Attention

Previously every configuration diagnostic reported under the single config code, so ignore = ["config"] suppressed the whole channel. The fine-grained codes are now the real lint codes, and a bare "config" entry matches only the code-less configuration warnings — replace it with "config.*" to keep suppressing everything.

Needs-domain diagnostics (needs.*)

Most needs.* codes are documented beside the directive or configuration key they concern. The sphinx-test-reports directives’ twelve codes — needs.test_report_option_missing, needs.test_report_option_invalid, needs.test_report_path_outside_root, needs.test_report_missing, needs.test_report_unreadable, needs.test_report_json_unsupported, needs.test_report_suite_not_found, needs.test_report_case_not_found, needs.test_report_need (warnings: the directive creates no need, except :auto_cases: alone and a field default needs.test_report_need reports on a created need), needs.test_report_duplicate_id (warning: a need of an :auto_suites: / :auto_cases: expansion is not created), needs.test_report_id_shadows_section (warning: a need’s id is also a section’s anchor on its page) and needs.test_report_note (info) — are listed in Diagnostics. The test-results and test-env tables reuse those that apply to them and add three of their own — needs.test_report_env_shape (an environment file, or one environment in it, that is not an object), needs.test_report_env_not_present (an :env: name the file does not hold) and needs.test_report_env_key_not_present (a :data: name a shown environment does not hold), all warnings — listed in Results tables. needs.filter (warning) is the code of a dynamic function whose filter cannot be evaluated on some of the needs it tests — one lacking a field the filter compares, say: calc_sum still counts them, check_linked_values still tests them and copy drops them, as sphinx-needs does, and the call is reported once, naming them. One more is worth naming here, because it is the only one raised by a whole-PROJECT check rather than by reading a single file:

  • needs.cypher_vocabulary — a view directive’s :cypher: (Cypher query) references a label, relationship type or property key that does not exist anywhere in the project’s needs graph. openCypher runs such a query silently — the missing name reads as null, so the view usually matches nothing or renders blank cells, though a null-tolerant test such as IS NULL can instead make it match everything — and this is what says so, anchored on the :cypher: option, with the same did-you-mean notes ubc query cypher prints.

    Like every whole-project code (std.duplicate_docname, the toctree.* family), it is reported by ubc check, counted by the ubc build html quality gate, and listed — with its position — in the editor extension’s diagnostics view. It is not an inline squiggle: the editor’s per-file underlines carry the parse and markup phases only, which is why the needs.cypher_invalid on the very same option does underline and this one does not.

    One warning per directive, carrying every note its query produced, because a :cypher: has a single source range for the whole query:

    warning[needs.cypher_vocabulary]
      --> index.rst:9:5
      the needtable :cypher: query references vocabulary this project does not have: unknown label `ned` — did you mean `req`?; unknown property `staus` — did you mean `status`? (the query still runs — the missing name reads as null, so the result is not what the query says: usually no rows or blank cells, sometimes a full but wrong table)
    

    It is recomputed for the whole project on every index pass, so it disappears by itself once the missing vocabulary exists — you do not have to re-save the file it was raised on. A directive written in an .. include::``d fragment is reported against the FRAGMENT, with the including page named alongside it, so a ``per-file-ignores glob on either path suppresses it. A known field that merely happens to be unset on some needs never raises it: the check tests the project’s schema, never any need’s data.

    Narrow it with contains = "unknown label", scope it to a path with per-file-ignores ("index.rst" = ["needs.cypher_vocabulary"]), or silence the class with ignore = ["needs.cypher_vocabulary"]. message-ignores matches the message TEXT only, so a rule naming a position such as contains = "index.rst:9:5" never fires — use per-file-ignores for that. It is distinct from needs.cypher_invalid, which reports a query that does not parse or has the wrong shape — a different fault with a different fix, so the two are separately suppressible.

per-file-ignores

Type: object (default: {})

A mapping of glob patterns to lists of diagnostic codes. When a diagnostic’s source file matches a pattern, and the diagnostic code is in the associated list, the diagnostic is suppressed.

Paths are specified in POSIX format (forward slashes) and are relative to the configuration file directory, ensuring cross-platform compatibility. During configuration resolution, patterns are absolutised relative to the directory of the config file that defines them. This means patterns from an extend-ed parent config are correctly resolved relative to the parent’s location, not the child’s.

Standard glob syntax is supported (*, **, ?, [...]). Invalid glob patterns are reported as configuration diagnostics and skipped.

A diagnostic with multiple file-based source locations is suppressed if any of its file locations matches at least one rule for the code.

A finding about content that was spliced into a page is suppressed by a rule matching any file it names, exactly as above — so the only question is which files that is:

  • An included fragment — spliced by .. include:: — names the fragment and the page it was spliced into (the included-from line the report prints beside it), so a rule naming either path suppresses it.

  • Prologue and epilogue content (rst_prolog / rst_epilog) has no file of its own — it is written in ubproject.toml — so the finding names the page, labelled (in rst_prolog), and a rule naming the page suppresses it.

  • A card whose need has a home document — a need shown by a needextract — names that document and not the page, so a rule naming the home document suppresses it and a rule naming the page the card appears on does not.

  • A card with no home document — an imported need, a generated report — names the page itself with a label ((imported), (in needreport)), so a rule naming the page still suppresses it.

The same rule decides suppression in ubc check, in ubc build html and in ubc build pdf, for per-finding diagnostics and for the aggregated build.directive_unhandled-style counts alike — in the aggregated case it is applied to each counted site, so a rule removes the sites it names from the count and leaves the rest reported.

[lint.per-file-ignores]
# Ignore dead links and field validation errors in test fixtures
"tests/**/*.rst" = ["needs.dead_link", "needs.invalid_field_value"]
# Ignore schema violations in legacy documentation
"legacy/**" = ["needs.schema_definition_violation"]
# Ignore duplicate needs in a specific file
"docs/generated/index.rst" = ["needs.duplicate"]

message-ignores

Type: array of objects (default: [])

A list of rules for suppressing diagnostics whose message contains a given substring. Each rule is an object with:

  • codes (optional): a list of diagnostic codes this rule applies to. If omitted or empty, the rule applies to all diagnostic codes.

  • contains: a case-insensitive substring to match against the diagnostic message. If omitted or empty, the rule matches all messages for the specified codes (or all diagnostics if codes is also omitted).

This is useful for suppressing specific classes of validation errors, for example diagnostics about a known deprecated field.

# Suppress all diagnostics mentioning "deprecated_field"
[[lint.message-ignores]]
contains = "deprecated_field"

# Suppress only field validation errors mentioning a specific pattern
[[lint.message-ignores]]
codes = ["needs.invalid_field_value"]
contains = "legacy_option"

Source-mount confinement (build.mount_path_escape)

build.mount_path_escape reports a document contributed by a [[source.mounts]] tree that references a file outside that tree — an include, an image, a download or a diagram :file: that climbs out of the mount.

Despite the build. prefix it is an indexing diagnostic, anchored at the referring document, so unlike the ubc build html warnings below per-file-ignores does apply to it. That is the only way to hold two mounts to different policies, because ignore is project-wide per code:

[lint.per-file-ignores]
# Off for one trusted generated tree, still on for every other mount.
"generated/**" = ["build.mount_path_escape"]

# …or off for the whole project.
# [lint]
# ignore = ["build.mount_path_escape"]

Its severity is fixed at warning level. To make it fail a build, escalate with [build.html] deny — deny = "warning" turns this (and every other warning) into a non-zero exit.

Files no root claims (std.file_outside_roots)

Added in version 0.33.0.

std.file_outside_roots reports a file that matches your source patterns but that no root claims: it lies outside the project’s source root ([source] dir, or the deprecated [project] srcdir) and outside every [[source.mounts]] root. The message names whichever of the two keys your project actually writes. Discovery walks the declared roots and nothing else, so such a file is not discovered at all — it is not indexed, it produces no page, and no :doc: or toctree entry can name it. This warning is the only thing that will tell you it is there.

It can only arise when the source root is an explicit subdirectory (or a directory beside the configuration folder): left unset, the source root is the configuration file’s own directory, which claims every file below it. For that default layout ubCode does not even look, so the check costs nothing.

Three ways to act on it, and they are what the message names:

  • move the file under the source directory (or under a mount root), which makes it a document;

  • add it to [source] extend_exclude, which stops discovering it — the right answer for a README.md sitting beside the project;

  • .. include:: it from a document, which is the right answer for a fragment. A file some document already pulls in this way is exempt and never reported: a fragment has no document name on purpose.

Use extend_exclude, not exclude. exclude is the base list, so setting it replaces the default hygiene patterns (.git, node_modules, _build, dist, .venv and the rest) — which would silence the file you named and start looking inside a vendored tree instead, earning a fresh std.file_outside_roots pointing inside it. extend_exclude adds to the defaults rather than replacing them.

The patterns this check honours are matched against the configuration folder — the tree it scans — rather than against a source root. That holds under [source] dir too, where the same patterns anchor at each source root for discovery: this pass walks the configuration folder, and a pattern compiled against a root that is not the tree being walked would exclude nothing here. It is the one place the configuration folder still decides anything once discovery follows the roots, and it is what makes a pattern you wrote to keep a directory out of your project keep it out of this report too.

Like build.mount_path_escape above it is an indexing diagnostic anchored at the file itself, so per-file-ignores applies — which is how one tree of loose files is silenced while the rest of the project stays covered:

[lint.per-file-ignores]
# Off for one directory of notes, still on everywhere else.
"notes/**" = ["std.file_outside_roots"]

# …or off for the whole project.
# [lint]
# ignore = ["std.file_outside_roots"]

Ignoring this code project-wide also skips the filesystem pass that feeds it. This check is a second, names-only walk of the configuration folder — it reads no file, but on a large repository it is not free — so a project that has silenced it stops paying for it as well. It is the one place a [lint] setting changes what ubCode reads from disk, and it is deliberate: a monorepo that does not want the check should not be charged for it on every build. per-file-ignores does not skip the pass, because a project that silences one directory still wants the report for the rest.

One class of file this check does not cover: a file under a mount root inside the configuration folder that the mount’s own walk does not yield. A mount claims its whole subtree, so such a file is “claimed” here even though nothing discovers it. Two things put a file in that state. The first is the mount’s own exclude — that absence is what your own mount configuration asked for, rather than something to warn you about. The second is a source or parser include pattern written with a directory prefix (vendor/*.rst): such a pattern is matched relative to the mount’s own root inside a mount, so it can stop matching there even though you named the file. If a file under a mount root has gone missing and you did not exclude it, check your include patterns for a directory prefix.

Its severity is fixed at warning level, and [build.html] deny escalates it like any other.

Variant-excluded toctree entries (toctree.variant_excluded)

Added in version 0.33.0.

toctree.variant_excluded reports a toctree entry naming a document that [[source.variant_sources]] removed from the current variant’s document set. The message names both the document and the if string that removed it, so the rule to narrow is the one you are told about.

It replaces two codes that would otherwise be misleading: toctree.nonexisting_document for an explicit entry (the name is not a mistake — the page exists in another variant), and toctree.empty_glob for a :glob: entry whose only matches were removed (the pattern is not empty — this variant emptied it).

It is graded info, not warning, and does not fail ubc check. In a 150% model a shared index that lists every edition’s pages is the normal shape, so this fires on configurations that are entirely correct — once per entry, on every build of every variant. As a warning it would have failed the default exit policy on such a project, and the obvious response would have been to ignore the code, which switches the reclassification off for the case it exists to catch: a rule glob that matched more than its author intended. Info keeps the message where you can read it and leaves the exit code alone.

Silencing it is therefore cosmetic rather than a way to unblock a build — it removes the message from the output, and nothing else:

[lint.per-file-ignores]
# Quieter for the one index that lists every edition's pages.
"reference/index.rst" = ["toctree.variant_excluded"]

# …or quiet for the whole project.
# [lint]
# ignore = ["toctree.variant_excluded"]

Consider leaving it on: it is the only remaining place a too-broad rule is visible, because a removed file is gone from every other product — no page, no document name, no needs, nothing in needs.json. If you want it enforced, [build.html] deny with deny = "info" escalates it like any other finding.

The diagnostic is anchored at the containing document, so per-file-ignores globs on the document that holds the toctree — not on the excluded page, which this variant does not have.

Ambiguous inventory matches (intersphinx.ambiguous)

intersphinx.ambiguous reports a cross-reference that matched more than one loaded intersphinx project — or more than one object type — after finding nothing in the project itself. It fires on :ref:, :term:, :doc:, :any:, a sphinx-design link typed any, and the unqualified :external: form; a target already qualified with a project key (:ref:`zulu:shared`, :external+zulu:ref:`shared`) names one inventory and cannot be ambiguous. The message says which project won and which also matched:

multiple inventory matches for 'shared': took 'zulu' (std:label), also matched by 'alpha' (std:label)

It is graded info, not warning, and never fails a build: the reference resolves, to exactly the page it always did, and a sphinx-build is silent about the collision entirely. Two overlapping inventories are also normal — every Sphinx-generated objects.inv ships the same handful of built-in labels — so this fires on configurations that are perfectly correct.

Read it as a note about your configuration rather than a fault in the page. It is worth reading because of how the winner is chosen: the inventories are merged in project-name order, and the alphabetically last project wins a collision. Adding an inventory whose key sorts after an existing one therefore moves every colliding link, silently — and so does renaming a project key. The order the projects appear in ubproject.toml has no effect.

To pin one target, qualify it with the project key:

:ref:`zulu:shared`
:external+zulu:ref:`shared`

To silence the notice instead:

[lint.per-file-ignores]
# Quiet for the one page that references the overlapping project.
"reference/api.rst" = ["intersphinx.ambiguous"]

# …or quiet for the whole project.
# [lint]
# ignore = ["intersphinx.ambiguous"]

If you want it enforced, [build.html] deny with deny = "info" escalates it like any other finding.

The equivalent collision reached through a Markdown link ([](#target) or [](inv:…)) reports intersphinx.ref at warning level instead, and fails a default build — because myst-parser warns on it and this side of the family follows its own upstream. The two codes are silenced separately.

Build warnings

Added in version 0.31.0.

The build.* warnings that ubc build html reports (build.directive_unhandled, build.search_index_large, and the rest) go through this same configuration: ignore suppresses one by code, and message-ignores narrows a code to particular messages — for instance a single unhandled directive name.

per-file-ignores does not apply to them: a build warning is a whole-build finding with no source file to match against.

Narrowing happens before gating: a code silenced here never reaches the quality gate that decides the exit code of ubc build html, so silencing a code is enough to keep a build green without lowering the project’s bar.

Changed in version 0.31.1: A warning belonging to one page now names it, as a docname: prefix on the message:

warning[build.video_glob_unsupported]
  guide/intro: a video source cannot be a wildcard pattern ...

The rest of the message is unchanged, so a message-ignores pattern written against the message text keeps matching, and you can narrow one to a single page by including the prefix (contains = "guide/intro: a video source").

This applies to the warnings raised while rendering a particular page. The unhandled-markup radars (build.directive_unhandled, build.role_unhandled) and the diagram-decline codes (build.diagram_file_unresolved, build.diagram_unconvertible, build.plantuml_render_failed) are also raised during rendering but are site-wide aggregates over every page, so they carry no prefix and name their example positions inside the message instead; nor do the whole-build warnings (asset reads, stale files, the manifest write), which belong to no page.

Two codes are the exception on the page-scoped side: build.need_view_failed and build.need_view_truncated name their own position as docname:line: (with the line number), so they are not given a second prefix — a narrowing pattern for either must use that form (contains = "guide/intro:42:"), not docname: plus a space.

Changed in version 0.31.2: build.need_view_truncated joins build.need_view_failed as a code that names its own docname:line: position. It is raised once per view directive whose :max_items: cap (its own, or the project-wide needs.views_max_items default) dropped items from the rendered result:

warning[build.need_view_truncated]
  index:16: view directive needlist #0 was truncated: showing the first 2 of 3 needs ...

Narrow it to one page the same way as its sibling — contains = "index:16:" — or silence the whole code with ignore = ["build.need_view_truncated"]. Like every build warning it counts toward the quality gate, so on the default bar a truncated view fails ubc build html until it is narrowed here or the cap is raised.

Added in version 0.36.0: Two of the redirects warnings are the ones most likely to want narrowing, because both are judgements about anchors rather than about the entry itself:

build.redirect_anchor_unknown

The target page renders no such section. The check reads the ids the rendered page actually carries — section slugs, generated ids, and anchors written by hand in a raw block — so a finding here is nearly always a real typo or a section that moved again. What it cannot see is an id a script injects at runtime, which is the honest reason to silence it (along with a deliberate placeholder for a section that is about to exist).

build.redirect_anchor_shadowed

The key’s page still renders the section the entry says has moved, so the entry is inert. Silence it only when you are keeping the entry on purpose — for instance while a rename is rolled out across several repositories.

Narrow either to a single entry with message-ignores, using the spelling that message carries. build.redirect_anchor_unknown names both the entry’s key and the target it points at (contains = "guide/old#setup" for the key, contains = "guide/new#install" for the target), which is what lets you narrow it when several entries point at one page. build.redirect_anchor_shadowed names the key only (contains = "guide/setup#old-name"). Neither carries a location, so per-file-ignores cannot reach them.

Added in version 0.31.2: build.need_style_invalid is raised once per need per page whose :style: carried a token that is not a valid CSS identifier (an ASCII letter or underscore, then ASCII letters, digits, underscores or hyphens) — so a need carded on two pages is reported for each. Such a token becomes no class on the card; valid tokens on the same need still apply:

warning[build.need_style_invalid]
  index: need REQ_1: ignored invalid :style: token(s) "9bad", "has space" ...

It is an ordinary page-scoped code — the message names the NEED and ubCode prefixes the page, so narrow it with contains = "index: need REQ_1" or silence the class with ignore = ["build.need_style_invalid"]. Like every build warning it counts toward the quality gate, so on the default bar a mistyped style fails ubc build html until it is fixed or narrowed here.

Added in version 0.31.2: build.need_layout_unknown is raised once per page and unknown name whose needs asked for a card design this project does not have — neither a built-in one nor one of its own [needs.card_layouts] designs. The affected needs are listed on the one line (the first five, then a count), because a mistyped [needs.fields.layout] default applies to every need in the project and would otherwise print hundreds of identical lines:

warning[build.need_layout_unknown]
  index: unknown :layout: "Focus" on 7 need(s) (REQ_1, REQ_2, REQ_3, REQ_4, REQ_5 and 2 more): rendered with the default layout "clean" (valid: clean, complete, focus, debug, clean_l, ...; aliases: test, focus_f)

The valid: list is built from your project: the built-in designs first, then any design [needs.card_layouts] defines, so a design you add appears in the message with no second edit. The named default is your project’s default_layout, so the message says which design the card actually got.

The card renders with the default clean design; a Sphinx-Needs build raises SphinxNeedLayoutException here, which ends the build. It is an ordinary page-scoped code — the message names the NEEDS and ubCode prefixes the page, so narrow it with contains = 'unknown :layout: "Focus"' or silence the class with ignore = ["build.need_layout_unknown"]. Like every build warning it counts toward the quality gate, so on the default bar a mistyped layout fails ubc build html until it is fixed or narrowed here.

Note

When using extend to inherit from a parent config, per-file-ignores and message-ignores use replace semantics: if the child config sets the field, it completely replaces the parent’s value (rather than merging additively). This is consistent with the behaviour of ignore and lsp_select. To combine parent and child rules, repeat the parent’s rules in the child config.

Deprecated since version 0.18.0: The rst_lint configuration section has been deprecated. Use lint instead for all linting configuration.