---
title: Lint Rules
description: >
Reference catalogue of every built-in Panache lint rule, the diagnostic codes
each rule emits, severity, auto-fix availability, and configuration
requirements.
---
This page documents every built-in lint rule that Panache ships with. Each
section lists the rule's configuration name (used in `[lint.rules]`), the
diagnostic codes the rule may emit at runtime, severity, auto-fix support, and
any extension or metadata requirements.
For a user-friendly introduction to the linter, CLI usage, and configuration,
see the [Linting guide](../guide/linting.qmd).
## Diagnostic format
Diagnostics are displayed in a compiler-style format:
```
severity[diagnostic-code]: message
--> file:line:column
```
Components:
`severity`
: `error`, `warning`, or `info`
`diagnostic-code`
: The specific code emitted (e.g., `undefined-reference-label`). A single rule
may emit several distinct codes.
`message`
: Human-readable description of the issue
`location`
: File path, line number, and column number
Some diagnostics include additional notes pointing at related locations:
```
warning[duplicate-reference-labels]: Duplicate reference definition 'link1'
--> document.qmd:4:1
note: First defined here:
--> document.qmd:3:1
```
## Severity levels
Panache uses three severity levels:
`error`
: Critical issues that prevent correct parsing or rendering
`warning`
: Likely mistakes or best practice violations
`info`
: Informational messages (currently unused, reserved for future use)
## Rules
### `heading-hierarchy` {#heading-hierarchy}
Detects skipped heading levels that violate document structure best practices.
Severity
: Warning
Auto-fix
: Yes
Diagnostic codes
: [`heading-hierarchy`](#heading-hierarchy)
Description
: Headings should increment by at most one level (e.g., H1 → H2 → H3).
Skipping levels (H1 → H3) makes the document structure unclear and can break
table-of-contents generation.
**Example violation:**
```markdown
# Main Title
### Subsection
```
**Diagnostic:**
```
warning[heading-hierarchy]: Heading level skipped from h1 to h3; expected h2
--> document.qmd:3:1
|
3 | ### Subsection
| ^^^^^^^^^^^^^^
```
**Auto-fix:** Changes `### Subsection` to `## Subsection`.
### `heading-eaten-attrs` {#heading-eaten-attrs}
Detects HTML comments that cause pandoc to silently drop a heading's `{...}`
attribute block.
Severity
: Warning
Auto-fix
: No
Diagnostic codes
: [`heading-eaten-attrs`](#heading-eaten-attrs)
Requirements
: `extensions.header-attributes = true` (the default for Pandoc, Quarto, and R
Markdown flavors).
Description
: Pandoc requires the `{...}` attribute block on a heading to be followed only
by whitespace. When any non-whitespace content (including an HTML comment)
follows the brace block, pandoc treats the braces as **literal text**,
silently dropping the attributes and producing an auto-generated id like
`"title-.unnumbered"`. Cross-references that targeted the intended id then
break with no diagnostic from pandoc itself.
**Example violation:**
```markdown
# Bibliography {.unnumbered} <!-- TODO -->
```
**Diagnostic:**
```
warning[heading-eaten-attrs]: Comment on a heading line with `{...}` attributes;
pandoc treats the brace block as literal text when anything follows it on the line.
--> document.qmd:1:30
|
1 | # Bibliography {.unnumbered} <!-- TODO -->
| ^^^^^^^^^^^^^
help: Move the comment to its own line before or after the heading.
```
**Resolution:** Move the comment to its own line. No auto-fix is provided
because the right placement (above or below the heading, or deleting the comment
entirely) is an author-intent decision.
### `heading-strip-comments-residue` {#heading-strip-comments-residue}
Detects HTML comments adjacent to a heading's `{...}` attribute block that would
leave stray whitespace under `pandoc --strip-comments`.
Severity
: Warning
Default
: Off --- opt in via `[lint.rules] heading-strip-comments-residue = true`.
Auto-fix
: No
Diagnostic codes
: [`heading-strip-comments-residue`](#heading-strip-comments-residue)
Requirements
: `extensions.header-attributes = true`.
Description
: When attributes still parse (the comment sits *before* the brace block),
invoking pandoc with `--strip-comments` removes the comment text but leaves
the surrounding whitespace. The resulting heading source has trailing or
interior whitespace adjacent to the attribute block, which can subtly affect
downstream tooling. This rule is opt-in because most authors do not use
`--strip-comments`; enable it when your publishing pipeline does.
**Example violation:**
```markdown
# Bibliography <!-- TODO --> {.unnumbered}
```
**Resolution:** Move the comment to its own line before or after the heading.
### `duplicate-reference-labels` {#duplicate-reference-labels}
Detects duplicate reference link and footnote definitions.
Severity
: Warning
Auto-fix
: No
Diagnostic codes
: [`duplicate-reference-labels`](#duplicate-reference-labels)
Description
: Each reference label and footnote ID must be unique within a document.
Duplicate definitions cause ambiguity---only the first definition is used,
making the others ineffective.
**Example violation:**
```markdown
See [link1] and [link2].
[link1]: https://example.com
[link1]: https://different.com
```
**Diagnostic:**
```
warning[duplicate-reference-labels]: Duplicate reference definition 'link1'
--> document.qmd:4:1
note: First defined here:
--> document.qmd:3:1
```
**Resolution:** Rename or remove the duplicate definition.
### `undefined-references` {#undefined-references}
Detects reference links and footnotes that point to missing definitions.
Severity
: Warning
Auto-fix
: No
Diagnostic codes
: [`undefined-reference-label`](#undefined-reference-label),
[`undefined-footnote-id`](#undefined-footnote-id)
Description
: Flags unresolved reference-style links (including shortcut/collapsed forms)
and unresolved footnote references. This helps catch broken cross-references
early in editing and CI.
**Example violation:**
```markdown
See [missing][nope] and note[^missing].
[ok]: https://example.com
```
**Diagnostic:**
```
warning[undefined-reference-label]: Reference label '[nope]' not found
--> document.qmd:1:15
warning[undefined-footnote-id]: Footnote '[^missing]' not found
--> document.qmd:1:31
```
#### `undefined-reference-label` {#undefined-reference-label}
Emitted when a reference-style link points to a label that has no matching
definition.
#### `undefined-footnote-id` {#undefined-footnote-id}
Emitted when a footnote reference points to an ID that has no matching footnote
definition.
### `undefined-anchor` {#undefined-anchor}
Detects inline links whose `#fragment` destination has no matching anchor in the
document.
Severity
: Warning
Auto-fix
: No
Diagnostic codes
: [`undefined-anchor`](#undefined-anchor)
Description
: Flags `[text](#fragment)` links where `#fragment` does not match any anchor
that will exist in the rendered output. Anchor sources include explicit
`{#id}` attributes on headings, fenced divs, code blocks, spans, and chunk
labels, plus auto-generated heading IDs (when the `auto_identifiers`
extension is enabled). Matching is case-sensitive, mirroring how browsers
resolve URL fragments.
Links with a path component (`other.qmd#frag`), absolute URLs
(`https://example.com#frag`), and bare back-to-top links (`#`) are not flagged.
In bookdown projects, sibling chapters are scanned because bookdown's gitbook
renderer rewrites cross-chapter anchors. Quarto books render each chapter to a
separate HTML page and are not scanned cross-chapter.
Anchors declared via raw HTML `<a id="x">` / `<a name="x">` are not currently
inspected, so links to them may be flagged as undefined. `<div id="x">` blocks
are recognized.
**Example violation:**
```markdown
# Real Heading {#real}
See [the typo](#reel).
```
**Diagnostic:**
```
warning[undefined-anchor]: Anchor '#reel' not found in document
--> document.qmd:3:16
```
### `unused-definitions` {#unused-definitions}
Detects reference labels and footnote definitions that are declared but never
referenced.
Severity
: Warning
Auto-fix
: No
Diagnostic codes
: [`unused-definition-label`](#unused-definition-label),
[`unused-footnote-id`](#unused-footnote-id)
Description
: Flags unused reference definitions (`[label]: ...`) and unused footnote
definitions (`[^id]: ...`). This helps keep documents tidy and avoids dead
references that can accumulate over time. When project metadata is available
(for example in Quarto/Bookdown project lint runs), usage is resolved across
project documents to reduce cross-file false positives.
**Example violation:**
```markdown
Text with one note[^1].
[^1]: Used note.
[^2]: Unused note.
[used]: https://example.com
[unused]: https://unused.example.com
```
**Diagnostic:**
```
warning[unused-footnote-id]: Footnote '[^2]' is never used
--> document.qmd:4:1
warning[unused-definition-label]: Reference definition '[unused]' is never used
--> document.qmd:7:1
```
#### `unused-definition-label` {#unused-definition-label}
Emitted when a reference definition (`[label]: ...`) is declared but never
referenced anywhere in the document (or project, when project metadata is
available).
#### `unused-footnote-id` {#unused-footnote-id}
Emitted when a footnote definition (`[^id]: ...`) is declared but never
referenced.
### `citation-keys` {#citation-keys}
Validates citation keys against loaded bibliographies and detects conflicts in
inline bibliography entries.
Severity
: Error for bibliography load/parse failures, Warning for undefined keys and
duplicates
Auto-fix
: No
Requirements
: Requires `extensions.citations = true` in configuration
Diagnostic codes
: [`bibliography-load-error`](#bibliography-load-error),
[`bibliography-parse-error`](#bibliography-parse-error),
[`missing-bibliography-key`](#missing-bibliography-key),
[`duplicate-bibliography-key`](#duplicate-bibliography-key),
[`duplicate-inline-reference-id`](#duplicate-inline-reference-id)
Description
: Checks that all cited keys (`[@key]`) exist in the configured bibliography
files. Also validates inline bibliography entries for duplicates and
conflicts.
**Example violation (undefined key):**
```markdown
---
bibliography: refs.bib
---
See @smith2020 and @jones2021.
```
If `jones2021` doesn't exist in `refs.bib`:
```
warning[missing-bibliography-key]: Citation key 'jones2021' not found in bibliography
--> document.qmd:5:17
```
**Example violation (bibliography load error):**
```markdown
---
bibliography: nonexistent.bib
---
```
```
error[bibliography-load-error]: Failed to load bibliography nonexistent.bib: File not found
--> document.qmd:1:1
```
**When it runs:** Only when document metadata includes bibliography
configuration and the citation extension is enabled.
#### `bibliography-load-error` {#bibliography-load-error}
Emitted when a configured bibliography file cannot be opened (missing,
unreadable, etc.).
#### `bibliography-parse-error` {#bibliography-parse-error}
Emitted when a bibliography file is opened successfully but contains entries
that fail to parse.
#### `missing-bibliography-key` {#missing-bibliography-key}
Emitted when a `@cite` reference does not match any key in the loaded
bibliography.
#### `duplicate-bibliography-key` {#duplicate-bibliography-key}
Emitted when the same key appears more than once across loaded bibliography
files.
#### `duplicate-inline-reference-id` {#duplicate-inline-reference-id}
Emitted when an inline bibliography entry collides with another inline entry or
with a key from a loaded bibliography file.
### `crossref-as-link-target` {#crossref-as-link-target}
Detects link destinations that begin with `@`, which is almost always a typo for
`#` (anchor) or a misplaced cross-reference / citation key.
Severity
: Warning
Auto-fix
: Yes
Requirements
: Requires `extensions.citations = true` in configuration (default for the
Pandoc, Quarto, and R Markdown flavors).
Diagnostic codes
: [`crossref-as-link-target`](#crossref-as-link-target)
Description
: In Pandoc/Quarto, `@key` is reserved for citations and cross-references and
must stand alone, not appear inside a link's `(...)` destination. Writing
`[Figure 2](@fig-2)` produces a link with the literal URL `@fig-2`; the
author almost always meant `[Figure 2](#fig-2)`.
**Example violation:**
```markdown
See [Figure 2](@fig-2) for details.
```
**Diagnostic:**
```
warning[crossref-as-link-target]: Link target starts with '@'; cross-references and citation keys must stand alone, not appear as a link destination
--> document.qmd:1:16
|
1 | See [Figure 2](@fig-2) for details.
| ^
```
**Auto-fix output:**
```markdown
See [Figure 2](#fig-2) for details.
```
**When it runs:** On every inline link (`[text](dest)`) and inline image
(``) whose destination's leading non-whitespace character is `@`.
Bare cross-references (`@fig-2`) and citation forms (`[@smith2020]`) outside of
link destinations are not flagged.
### `chunk-label-spaces` {#chunk-label-spaces}
Detects executable chunk labels containing whitespace (for example
`{r several words}` or `label="several words"`).
Severity
: Warning
Auto-fix
: No
Diagnostic codes
: [`chunk-label-spaces`](#chunk-label-spaces)
Description
: Labels with spaces are accepted by Quarto execution, but cross-references
often fail to resolve reliably. Use a stable identifier such as
`several-words` or `several_words` instead.
### `missing-chunk-labels` {#missing-chunk-labels}
Detects executable chunks that do not define a `label` (either inline or
hashpipe style).
Severity
: Warning
Auto-fix
: No
Diagnostic codes
: [`missing-chunk-labels`](#missing-chunk-labels)
Description
: Labels facilitate debugging. Add a label with either `#| label: my-chunk` or
inline `label=my-chunk`.
### `figure-crossref-captions` {#figure-crossref-captions}
Detects figure cross-references that point to chunk labels without a figure
caption option.
Severity
: Warning
Auto-fix
: No
Diagnostic codes
: [`figure-crossref-captions`](#figure-crossref-captions)
Description
: Bookdown figure cross-references (`\@ref(fig:...)`) require a captioned
chunk to create a resolvable figure label at render time. When the target
chunk has a `label` but no `fig-cap`/`fig.cap`, the crossref will not
resolve.
### `unknown-emoji-alias` {#unknown-emoji-alias}
Detects `:alias:` emoji shortcodes that are not recognized.
Severity
: Warning
Auto-fix
: No
Requirements
: Requires `extensions.emoji = true` in configuration
Diagnostic codes
: [`unknown-emoji-alias`](#unknown-emoji-alias)
Description
: Checks parsed emoji aliases against the emoji shortcode dataset and warns
when an alias is unknown.
**Example violation:**
```markdown
Looks good :smile:, but this one is wrong :not-a-real-emoji:.
```
**Diagnostic:**
```
warning[unknown-emoji-alias]: Unknown emoji alias ':not-a-real-emoji:'
--> document.qmd:1:40
```
### `html-entities` {#html-entities}
Detects malformed HTML named entity references in inline prose.
Severity
: Warning
Auto-fix
: No
Diagnostic codes
: [`html-entities`](#html-entities)
Description
: Pandoc and Quarto pass HTML named entities like `…` through to the
output unchanged, so a typo (`&ellips;`) or a missing trailing semicolon
(`&numero` instead of `№`) silently produces wrong output. This rule
flags three conservative cases:
- `&NAME;` where `NAME` is not in the [HTML5 named-entity
table](https://html.spec.whatwg.org/multipage/named-characters.html).
- `&NAME` (no semicolon) where adding the semicolon would produce a known
entity. This avoids firing on plain prose like "Tom & Jerry" or "AT&T",
since those words are not entity names.
- `&NAME` (no semicolon, length ≥ 4) where `NAME` is one edit away from a
known entity (e.g. `&hellp` → `…`). Far-from-anything words are
left alone to keep prose like "Procter &Gamble" quiet.
The rule deliberately ignores numeric character references (`{`,
`ꯍ`) for now and does not scan code spans, code blocks, raw HTML,
inline/display math, link destinations, attributes, YAML metadata, or comments.
**Example violations:**
```markdown
This is &ellips; wrong.
Section &numero 5 of the report.
```
**Diagnostics:**
```
warning[html-entities]: Unknown HTML entity '&ellips;'
--> document.qmd:1:9
= help: did you mean '…'?
warning[html-entities]: HTML entity '&numero' is missing a trailing ';'
--> document.qmd:3:9
= help: write '№' to encode the character
```
### `adjacent-footnote-refs` {#adjacent-footnote-refs}
Detects footnote references placed back-to-back (`[^a][^b]`) where the rendered
superscripts run together (e.g. footnotes 7 and 8 look like footnote 78).
Severity
: Warning
Auto-fix
: Yes (inserts a space between the references)
Requirements
: Requires `extensions.footnotes = true` in configuration
Diagnostic codes
: [`adjacent-footnote-refs`](#adjacent-footnote-refs)
Description
: When two footnote references appear with no intervening character, most
renderers emit the superscript markers as a single visually-merged run.
Inserting a single space between them keeps the markers distinct without
changing the prose.
**Example violation:**
```markdown
See the prior reports[^a][^b] for context.
```
**Auto-fix output:**
```markdown
See the prior reports[^a] [^b] for context.
```
### `footnote-ref-in-footnote-def` {#footnote-ref-in-footnote-def}
Detects footnote references (`[^id]`) that appear inside a reference-style
footnote definition body, where pandoc silently parses them as literal text
instead of resolving the reference.
Severity
: Warning
Auto-fix
: No (the user must decide whether to inline the prose, restructure to lift
the reference out of the definition body, or drop it)
Requirements
: Requires `extensions.footnotes = true` in configuration (default for Pandoc,
Quarto, R Markdown, and GFM flavors).
Diagnostic codes
: [`footnote-ref-in-footnote-def`](#footnote-ref-in-footnote-def)
Description
: Pandoc footnotes do not nest. Inside a `[^x]: ...` definition body, any
`[^id]` reference is silently parsed as a literal `Str` --- the would-be
link disappears from the output with no warning. The same applies to
references nested arbitrarily deep inside that body (inside emphasis,
strong, strikeout, links, blockquotes, lists, or inline footnotes).
This rule surfaces the silent drop at lint time so the user notices before the
document is rendered. After the parser fix that aligns panache with pandoc on
this case, the inner references no longer appear as `FOOTNOTE_REFERENCE` nodes;
the rule scans the definition body's `TEXT` tokens directly for `[^id]` byte
patterns, which naturally skips code spans, math, raw HTML, and other
CST-distinct constructs.
References at the top level (outside any definition body) and inside a top-level
inline footnote `^[...]` are not flagged --- pandoc resolves those normally.
**Example violation:**
```markdown
Outer[^a].
[^a]: Body has [^b] ref and **bold [^c] inside** wrapper.
[^b]: B body.
[^c]: C body.
```
**Diagnostic:**
```
warning[footnote-ref-in-footnote-def]: Footnote reference '[^b]' inside a footnote definition body
is silently dropped by pandoc (rendered as literal text)
--> document.qmd:3:16
= help: footnotes do not nest in pandoc; inline the prose, restructure to
keep the reference outside the definition body, or remove it
```
### `stray-fenced-div-markers` {#stray-fenced-div-markers}
Detects stray closing fenced div markers (`:::` or longer runs of colons) that
do not pair with an opening fence and consequently land inside paragraph text.
Severity
: Warning
Auto-fix
: No
Requirements
: Requires `extensions.fenced-divs = true` in configuration (default for
Pandoc, Quarto, and R Markdown flavors).
Diagnostic codes
: [`stray-fenced-div-markers`](#stray-fenced-div-markers)
Description
: Pandoc fenced divs use `:::` (or longer colon runs) for both openers (with
an attribute or class) and closers (bare colons). When a closer-shaped line
has no matching opener in scope, Pandoc treats it as ordinary paragraph text
instead of as a div marker. This is almost always a typo---a mismatched
fence count between opener and closer, an accidentally deleted opener, or a
copy/paste artifact---and the document silently renders with `:::` showing
up in the prose.
Quarto's runtime emits a warning when it sees stray `:::`, but exits 0, which
makes the issue invisible to CI/Makefile workflows. This rule fills that gap by
flagging the same condition at lint time.
The rule fires when, after up to three leading spaces, a line in a paragraph
consists of three or more colons followed only by trailing whitespace. It does
**not** flag mid-line `:::`, code spans containing `:::`, or indented code
blocks---those are not closing-fence shapes.
**Example violation:**
```markdown
::: warning
The fence count on the opener and closer don't match.
::::
```
**Diagnostic:**
```
warning[stray-fenced-div-markers]: Stray fenced div marker '::::' has no matching opener
--> document.qmd:3:1
= help: if this is meant to close a div, check the opener's class/attributes;
otherwise escape the colons or rewrite the line
```
## YAML diagnostics
Panache emits YAML diagnostics when embedded YAML content is invalid. These
apply to both document frontmatter (`--- ... ---`) and executable chunk hashpipe
options (`#| ...`).
### `yaml-parse-error` {#yaml-parse-error}
Severity
: Warning
Auto-fix
: No
Description
: The YAML lexer/parser could not interpret the content (malformed flow
sequences, unterminated strings, etc.).
**Example (hashpipe):**
````markdown
```{{r}}
#| echo: [
1 + 1
```
````
**Diagnostic:**
```
warning[yaml-parse-error]: YAML parse error: ...
--> document.qmd:2:10
```
### `yaml-structure-error` {#yaml-structure-error}
Severity
: Warning
Auto-fix
: No
Description
: The YAML parsed successfully but its top-level shape is not valid for the
context (for example, frontmatter that is not a mapping, or a hashpipe block
that does not produce a mapping of options).