Skip to content

Fix quarto.version crash on build-metadata version strings - #14894

Merged
cderv merged 6 commits into
mainfrom
fix/version-lua-string-table-repr
Sep 16, 2026
Merged

cderv merged 6 commits into
mainfrom
fix/version-lua-string-table-repr

Conversation

@cderv

@cderv cderv commented Sep 16, 2026

Copy link
Copy Markdown
Member

Context: this is to prepare to #14706

When quarto is built with a version string containing semver build metadata (e.g. 1.9.13+test.20260910, as the built-binary CI test workflow stamps, or as a distro packager appending a revision suffix might produce), rendering any document that reads quarto.version or quarto.config.version() as a table crashes with bad argument #1 to 'concat' (table expected, got string) -- e.g. the {{< version >}} shortcode (src/resources/extensions/quarto/version/version.lua:3).

Root Cause

init.lua's version() (backing both quarto.version and quarto.config.version()) tries to construct a pandoc.types.Version from the raw version string and falls back to returning the plain string when construction fails. pandoc.types.Version's parser only accepts dot-separated non-negative integers -- it rejects build metadata and any string without a leading digit -- so any consumer assuming a table crashes on that fallback.

Fix

version() now extracts the leading dot-separated numeric component of the version string before constructing the Version object, so construction always succeeds and a plain string is never returned. Falls back to Version('0') when no leading digit is present. Handles versions with any number of dotted components, not just three.

Alternative considered: making version.lua (or other individual consumers) tolerate both a table and a string. This was rejected since it only patches one call site and leaves the same crash reachable from quarto.config.version() and any third-party extension reading quarto.version.

A pandocDataDirArgs() helper was extracted in src/core/resources.ts so the new regression test can invoke pandoc directly against the same --data-dir a real render uses.

….version()

init.lua's version() silently returned the raw version string whenever
pandoc.types.Version() failed to parse it, making quarto.version and
quarto.config.version() polymorphic: a Version object on success, a plain
string otherwise. pandoc.types.Version's dotted-integer parser rejects any
string with a "+" (semver build metadata) or without a leading digit, which
consumers like the `{{< version >}}` shortcode (version.lua) don't expect --
it unconditionally calls table.concat(quarto.version, '.'), which crashes
with "bad argument #1 to 'concat' (table expected, got string)" whenever the
string fallback is hit.

This is reachable outside of CI too: QUARTO_FORCE_VERSION is a user-settable
env override, and quarto-bld prepare-dist --set-version accepts an arbitrary
string, so a distro packager appending a revision suffix could hit the same
crash on a real build.

Extract the leading dot-separated numeric component of the version string
before constructing the Version object, so parsing always succeeds and the
string-fallback branch becomes unreachable. Both quarto.version and
quarto.config.version() go through the same local version() function, so
one fix covers both call sites.
The previous fix extracted the leading numeric prefix from a version
string with a fixed-arity pattern chain (^%d+%.%d+%.%d+, then
^%d+%.%d+, then ^%d+), which silently truncates any version with more
than three dot-separated numeric components -- e.g. "1.2.3.4" became
"1.2.3". QUARTO_FORCE_VERSION and quarto-bld prepare-dist --set-version
both accept arbitrary strings, so a 4+ component version is reachable,
not just hypothetical.

Replace the pattern chain with leadingDottedNumber(), which walks the
string collecting all leading dot-separated digit runs instead of
matching a hard-coded number of them. Strengthen the existing
regression tests to assert the exact concatenated value (not just that
table.concat didn't crash), and add a four-component case.
Quarto's own --data-dir resourcePath("pandoc/datadir") pair was
hardcoded independently in three places (crossref, pandoc render,
and the version-repr test), risking drift if the path ever changes.
@posit-snyk-bot

posit-snyk-bot commented Sep 16, 2026

Copy link
Copy Markdown
Collaborator

Snyk checks have passed. No issues have been found so far.

Status Scan Engine Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues
Licenses 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

@cderv
cderv merged commit 1a08171 into main Sep 16, 2026
51 checks passed
@cderv
cderv deleted the fix/version-lua-string-table-repr branch September 16, 2026 12:06
cderv added a commit that referenced this pull request Sep 17, 2026
…tcode string guard

PR #14894 rewrote init.lua's version() to always return a pandoc.types.Version,
normalizing the quarto-version filter param to its leading dotted-numeric
component. quarto.version can no longer be a plain string, so the tostring()
fallback added to version.lua for that case guards a condition that cannot
occur, and main's own quarto-version-repr test asserts table.concat succeeds
across plain, four-component, build-metadata, and non-numeric inputs.

The +test.<date> build-metadata marker on built test distributions is kept
deliberately (D2 in llm-docs/built-version-testing-architecture.md): it lets a
built test dist announce itself as a CI trial build in `quarto --version`
while keeping quarto-required range comparisons valid, and #14894 already
makes the marker invisible to Lua filters, which was the only real problem it
used to cause.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants