Skip to content

Fix keep-md cleanup deleting the markdown output when output-file has an .html extension - #14671

Merged
cderv merged 3 commits into
mainfrom
fix/14669-output-file-html-ext-multiformat
Jul 13, 2026
Merged

Fix keep-md cleanup deleting the markdown output when output-file has an .html extension#14671
cderv merged 3 commits into
mainfrom
fix/14669-output-file-html-ext-multiformat

Conversation

@cscheid

@cscheid cscheid commented Jul 11, 2026

Copy link
Copy Markdown
Member

Closes #14669.

Problem

Pairing an html format with a markdown format while output-file has an .html extension (e.g. output-file: index.html with format: html + commonmark, as nbdev sets up for llms.txt workflows) names the markdown output index.html.md — exactly the path the keep-md intermediate convention (<input-stem>.<base-format>.md, executionEngineKeepMd) assigns to the html format. The html render's cleanup removed what it took for a stale intermediate, deleting the freshly written markdown output:

  • website projects: the render then failed at the output-move step (NotFound … rename '<proj>/index.html.md' -> '<proj>/_site/index.html.md'), leaving index.html and site_libs/ stranded in the project root
  • default-type projects: exit 0, markdown output silently missing
  • keep-md: true: no crash, but the html render's intermediate silently fought with the markdown output for the same file

Diagnosis was confirmed by instrumenting renderCleanup and watching the filesystem at 2ms resolution: the commonmark output is written and deleted 29ms later by the html format's cleanup.

Fix

Principle: keep-md intermediate handling must never write to or delete a path that a declared format owns.

Commit 1 (core fix): compute the projected output paths of the formats being rendered (projectedOutputFile, next to formatOutputFile in core/render.ts), attach them to each RenderContext, and

  • skip the keep-md deletion in renderCleanup when the conventional keep-md path is another format's output
  • skip the keep-md intermediate write on the same collision, with a warning (the real output wins; warn rather than error for now, may revisit)

Commit 2 (partial-render protection, split out for review): a partial render (quarto render --to html) must not delete the markdown twin a previous full render produced (outputs live in place in default-type projects). Since renderContexts already resolves every declared format (inactive ones are marked active: false), the implementation is just dropping the active-only filter from commit 1's computation — one line plus a test.

Tests

New smoke tests in tests/smoke/render/render-output-file-collision.test.ts (fixtures under tests/docs/output-file-collision/):

  1. website project renders both outputs into _site, no root debris
  2. default-type project keeps the markdown output
  3. --to html partial render preserves an existing twin (commit 2)
  4. keep-md: true warns, and the twin holds the commonmark output rather than the intermediate

Each commit's tests verified to fail without that commit's change. Genuine stale-intermediate cleanup still works (keep-md: true render followed by keep-md: false removes the leftover). Together with #14670, the full nbdev/llms.txt scenario from #14667 now works end-to-end (verified on a local merge of both branches: render succeeds and the served page's index.html.md link resolves with HTTP 200).

🤖 Generated with Claude Code

cscheid and others added 2 commits July 10, 2026 19:55
When output-file has an .html extension and an html format is paired
with a markdown format (e.g. output-file: index.html with html +
commonmark, as nbdev sets up for llms.txt workflows), the markdown
output is named index.html.md -- the same path the keep-md intermediate
convention (<input-stem>.<base-format>.md) assigns to the html format.
The html render's cleanup removed the "stale intermediate", deleting
the freshly written markdown output: website renders then failed at
the output-move step, and other project types silently lost the file.

Compute the projected output paths of the formats being rendered and
attach them to each RenderContext; render cleanup now never deletes a
path that a format owns, and a keep-md: true render whose intermediate
location collides warns and skips saving the intermediate instead of
overwriting the output.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…14669)

A partial render (quarto render --to html) of a document whose other
declared format owns the keep-md-conventional path (output-file:
index.html + a markdown format -> index.html.md) was still deleting
the markdown output a previous full render produced, since outputs
live next to sources in default-type projects.

renderContexts already resolves every declared format (inactive ones
are simply marked active: false), so extending the protection is just
including inactive contexts when collecting the owned output paths.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@posit-snyk-bot

posit-snyk-bot commented Jul 11, 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.

@cscheid

cscheid commented Jul 11, 2026

Copy link
Copy Markdown
Member Author

(Claude says:)

Note on the 8 failing windows checks (analysis after re-running them — they fail identically across 3 attempts):

  • The failing tests are unrelated to this PR: babel/TinyTeX package-auto-install docs (13633.qmd, latex-hyphen-lang-{zh,es}.qmd), caption-footnotes, the feature-format matrix, and one search-index test. The new render-output-file-collision tests pass on both platforms, and every ubuntu job is green.
  • Root symptom: TeX Live's fmtutil.pl/tlmgr aborts with kpsewhich -var-value=TEXMFROOT failed / 'kpsewhich' is not recognized. The PATH it reports has its first two entries joined by : (the unix separator) instead of ;, merging both TinyTeX bin dirs into one invalid entry — this happens inside TeX Live's perl, far below anything this PR touches (keep-md path bookkeeping).
  • Why this PR and not Fix index.html clean-URL rewrite mangling alternate-format links on index pages #14670 (same runner image windows-2025-vs2026 20260628.158.1, same fresh TinyTeX v2026.07 download, green 36 minutes earlier): this PR adds a smoke test file, which reshuffles the deterministic test-bucket sharding; Fix index.html clean-URL rewrite mangling alternate-format links on index pages #14670 added only playwright files, which don't. A latent test-ordering-dependent TinyTeX corruption (e.g. bucket 10 runs render-pdf-svg-conversion / render-verapdf before the babel doc) would fire deterministically on exactly this PR's bucket layout — matching the 3-for-3 rerun failures while main and Fix index.html clean-URL rewrite mangling alternate-format links on index pages #14670 stay green.

I couldn't bisect the specific polluting test without a windows box; happy to file the CI investigation as a separate issue if this diagnosis looks right to maintainers.

The collision guard was inlined in both renderCleanup and the keep-md write path; keepMdCollidesWithFormatOutput keeps the invariant (never write to or clean up a path another format owns) in one place. Trim the changelog entry to the user-facing symptom -- the keep-md/output-move mechanism and the nbdev/commonmark example already live in the PR body.
@cderv

cderv commented Jul 13, 2026

Copy link
Copy Markdown
Member

LGTM.

Traced all four paths (default / website / --to html / keep-md): the collision check is source-relative on both sides (keepMd and projectedOutputFile), so it matches before the output-dir move, and pathsEqual keeps it separator-safe on Windows. Tests pass locally.

Two changes on top of the fix:

  • Pulled the duplicated collision check — it was inline in both renderCleanup and the keep-md write path — into keepMdCollidesWithFormatOutput in core/render.ts, so the "never touch a path another format owns" invariant has one home instead of two copies that can drift.
  • Trimmed the changelog entry to the user-facing symptom. The keep-md / output-move mechanism and the nbdev+commonmark example read as internals for a changelog line — that context is already in the PR body.

One thing I noticed while in here: book and manuscript renders call renderCleanup on their own paths and never populate siblingFormatOutputs, so an html+markdown output-file collision there would still hit the original bug. Out of scope for the reporter's case and rare. So I don't plan to follow up on this

@cderv
cderv merged commit d1ce415 into main Jul 13, 2026
51 checks passed
@cderv
cderv deleted the fix/14669-output-file-html-ext-multiformat branch July 13, 2026 15:39
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.

output-file with .html extension loses the markdown-format output in multi-format project renders (website render fails at move step)

3 participants