Skip to content

Conversation

@cderv
Copy link
Collaborator

@cderv cderv commented Jan 15, 2026

When rendering a .qmd document with embed-resources: true using an absolute path through a symlinked directory, the render fails during cleanup with:

ERROR: Refusing to remove directory /home/vis/cr173/tmp/quarto-bug/test_files
that isn't a subdirectory of /home/c/cr173/tmp/quarto-bug

Root Cause

The path mismatch occurs in this sequence:

  1. User provides symlinked path: /home/c/cr173/tmp/quarto-bug/test.qmd
  2. project.dir keeps the symlink path (Quarto's normalizePath() does NOT resolve symlinks)
  3. knitr resolves symlinks: R's rmarkdown:::abs_path(intermediates_dir) calls R's normalizePath() which DOES resolve symlinks
  4. Supporting path becomes real: /home/vis/cr173/tmp/quarto-bug/test_files
  5. safeRemoveDirSync() compares these paths as strings, which fails

The knitr symlink resolution happens in src/resources/rmd/execute.R:

supporting <- if (!is.null(intermediates_dir) && file_test("-d", intermediates_dir)) {
  rmarkdown:::abs_path(intermediates_dir)  # THIS RESOLVES SYMLINKS
} else {
  character()
}

The deeper issue is that src/core/deno/monkey-patch.ts replaces Deno.realPathSync with normalizePath, which intentionally does NOT resolve symlinks. This was done in commit 3bb0b45dc (Aug 2022) to work around a Windows UNC path bug where Deno.realPathSync returned malformed paths like UNC\x.org\... instead of \\x.org\....

That UNC bug was fixed in Deno v1.16 (denoland/deno#12243). Quarto now uses Deno 2.3.1+, so the original workaround is no longer needed.

Fix

Rather than removing the monkey-patch globally (which would require auditing all code paths), this fix:

  1. Creates src/deno_ral/original-real-path.ts that saves the original Deno.realPathSync before monkey-patching
  2. Imports it in src/quarto.ts BEFORE the monkey-patch import
  3. Modifies safeRemoveDirSync() to resolve both paths using the original realPathSync before comparison

The separate module with no imports avoids circular dependencies:

monkey-patch.ts → core/path.ts → deno_ral/fs.ts → monkey-patch.ts (CIRCULAR!)

This is a targeted fix that only affects the safety boundary check, with fallback to original paths if resolution fails.

Testing

  • Unit test added to tests/unit/ral/safe-remove-dir.test.ts for symlink handling
  • Smoke test added at tests/smoke/render/render-symlink-embed-resources.test.ts
  • All 206 existing unit tests pass

Follow-up Consideration

Since the UNC bug was fixed in Deno v1.16 (2021), and Quarto now uses Deno 2.3.1+, the monkey-patch may no longer be necessary at all. A follow-up issue could review whether the monkey-patch can be removed entirely, which would restore proper symlink resolution globally.

References:

Fixes #13890

…ked paths

Fixes rendering with `embed-resources: true` when the input path goes through
a symlinked directory.
@posit-snyk-bot
Copy link
Collaborator

posit-snyk-bot commented Jan 15, 2026

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

Status Scanner 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 marked this pull request as ready for review January 15, 2026 17:09
@cderv cderv marked this pull request as draft January 15, 2026 17:44
@cderv
Copy link
Collaborator Author

cderv commented Jan 15, 2026

I think I got the understanding right, but the resolution wrong looking at commit history...

I see f9cdaa9 with message

remove Deno.realPathSync from our codebase (keep monkey-patched version because deno's stdlib calls into it)

So it seems we don't want to use Deno.realPathSync at all ! However, we still have the problem of computation engine that could resolve symlinks in the resources and pass information to quarto 🤔

Like this happens with knitr. So I assumed we needed that.

@cscheid Do you remember the history behind f9cdaa9 ? I assumed maybe comestic to avoid having some Deno.fun eveywhere while working on deno_ral/ layer ?

Initially the monkey was done because of denoland/deno#12243 and I confirmed locally that the deno version we used solves this.

This PR change is scoped in the sense we do use Deno.realPathSync only for remove function.

but maybe we don't need to monkey-path after all the deno std itself.

Anyhow, I know you're on other focus, but it feels having another dev look would help me confirm my initial fix.

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.

Error rendering documents with embed-resources: true in sym-linked directories

3 participants