Skip to content

How Keystone checks your book

Keystone is a compiler for books, and like any compiler it checks your source as it builds. When something is wrong it tells you — on the terminal, naming the offending element, metadata key, or file and line — instead of silently letting it through.

This page is the map. Most messages on the terminal end with a See link to the page that covers them. One without a link is found below by the stage that raised it; Pandoc's own warnings carry none, so their entries quote a fragment of the line.

Two outcomes: warnings and errors

Every Keystone diagnostic is one of two severities.

  • WARN: — something is off, but the build has a safe fallback. The element is dropped or its bad input ignored, and you still get an artifact — unless you've enabled strict mode, where any warning fails the build.
  • ERROR: — the build cannot proceed correctly, so it stops and writes no completed artifact.

A lint finding opens with its location, manuscript/chapter.md:4:1: ERROR:, which your editor can open.

A line behind a │ bar is quoted rather than composed, and printed as given, so any line numbers or carets it carries still point where they should: a tool's output — Pandoc's math converter, the Lua loader, your operating system — or your own markup:

ERROR: 1 equation(s) cannot be converted for epub output
  │ Could not convert TeX math \begin{array}[b]{r} a \\ b \end{array}, rendering as TeX:
  │ \begin{array}[b]{r} a \\ b \end{array}
  │              ^
  │ unexpected "["

Some checks are Pandoc's rather than Keystone's — reading your Markdown, fetching an image, resolving a citation. Their warnings carry the same WARN: prefix as any other, so there is one shape to recognize. The entries below are marked (Pandoc) to tell you which tool to consult if you need to dig, and strict mode fails on them too. When Pandoc stops the build outright, the progress report marks the stage ✗, and Keystone closes with an ERROR: line naming the pass that stopped, Pandoc's own words quoted beneath it.

Where checks happen

Your book passes through stages, and each can fail in its own way. They run in the order below — the same order and the same names a build reports as it works (see Watching a build run). Starting the build falls before any stage runs, where a build has none to name yet. Anywhere holds what can stop any stage, and Importing a document is make import's, apart from any build.

Starting the build

Compiling your shortcuts

Linting your manuscript

Reading your Markdown

Silent: nothing reports it, so check the book

Pandoc reads the markup as plain text and builds no element for a check to inspect, so the build succeeds and the stray characters print in your book.

Rendering hooks

Expanding shortcuts

Saving hook assets

EPUB only, with a hook wired. What your hooks returned is saved for the pass that builds the book; nothing here stops a build.

Finding your fonts

EPUB only. The fonts your content names are noted for the archive to embed. A strict build's ✗ lands here for a warning Pandoc raised anywhere in the pass that reads your chapters.

Collecting your fonts

EPUB only. The font files your book uses are gathered for the archive to embed; nothing here stops a build.

Resolving configuration

Running handlers

What no shortcut attribute carries is checked here: an image's own width, a drop cap's opening paragraph, the heading the appendices start at. A message linking no page names its handler, as in dropcap:; see the shortcut's entry in the reference.

Composing headers & footers

Converting math

Embedding PDF metadata

Resolving citations

Writing your artifact

Promoting the artifact

Anywhere

Importing a document

The conversion reports as the one stage Converting to Markdown.

Finding the offender

A compiler points at a file and line, and so does Keystone's lint, which reads your source before Pandoc does. The checks after it can't: they run on the document Pandoc builds, and Pandoc's Markdown reader doesn't record where each element came from in your source.

Instead, a diagnostic about a div, span or heading quotes its markup: the fence, span or heading, over the text it opens with. The quote is rebuilt from what Pandoc read: a heading carries the identifier Pandoc gave it, values are quoted, and punctuation is typeset.

WARN: shortcut 'aside' rejects type 'todo'
  │ ::: {.aside type="todo"}
  │ A callout whose type is not one of the d…
  It takes one of the values listed.
  The default 'note' stands in.
  Valid: example, note, tip, warning
  See https://keystone.knight-owl.dev/errors/invalid-value/

Search your source for a class or a value from it: the manuscript, or shortcuts.yaml for markup a shortcut's template holds. A shortcut you used is quoted as you wrote it, even when a shortcut inside its template raised the warning. When the value the message names isn't in the quote, it's in that shortcut's definition.

A metadata check is the exception: it has no element to point at, so it names the offending pandoc.yaml key in the message itself (fontsize '13pt' is not a size this set allows) — search for the key.

Strict mode

While you're drafting, a warning shouldn't stop you — you want to see the page even with a placeholder that isn't quite right. So by default warnings are lenient: they print and the build continues.

Before you publish, flip that around. Set KEYSTONE_WARNINGS_AS_ERRORS=true in project.conf and every warning becomes fatal:

ERROR: warnings-as-errors is enabled and the build produced warnings:
  shortcut 'multicol' rejects cols '9'
  No new artifact was written.
  Fix the warnings above, or unset KEYSTONE_WARNINGS_AS_ERRORS for a lenient
  build.
  See https://keystone.knight-owl.dev/errors/#strict-mode

For a one-off check without editing config, make publish strict=true builds once in strict mode — see Publishing your book.

Strict mode fails the build on any warning and promotes no artifact. Where the warning came from decides how you hear about it. The filters collect theirs and report them together after the build, as above, so you fix them in one pass. A warning about your configuration is raised before any filter runs, and stops the build where it is raised, reporting only itself:

WARN: the project name is still the default "keystone"
  Set KEYSTONE_PROJECT, in project.conf or your environment, to name your own
  output.
  See https://keystone.knight-owl.dev/errors/default-project-name/
ERROR: warnings-as-errors is enabled
  The warning above stops the build.
  No new artifact was written.
  Fix the warning above, or unset KEYSTONE_WARNINGS_AS_ERRORS for a lenient
  build.
  See https://keystone.knight-owl.dev/errors/#strict-mode

The lint's warnings and Pandoc's own turn fatal too, and there the build stops on a ✗, with the tool's reason quoted, rather than with a warnings-as-errors message. Draft lenient, publish strict.

Silent by design

Not everything absent is a mistake. A few things are meant to be optional, so their absence is never reported:

  • Optional attribution. An epigraph or pull-quote with no source is a quote without an attribution line — a normal choice, not an error.
  • Undefined conditional symbols. ifdef/ifndef gate on whether a symbol is defined; an undefined symbol is simply false, so the content is omitted. That's the whole mechanism, not a failure. See Conditional content.

Separately, there is a small set of mistakes Keystone cannot see at all — malformed markup that never becomes an element. Know the limits — the mistakes Keystone can't catch.

Under the hood

How the checks are implemented — the closed vocabulary, the shared diagnostic path, and how a core template forker adds their own — is in Validation & diagnostics.