Skip to content

Fix Windows libPaths propagation via Rscript command line - #303

Open
shikokuchuo wants to merge 1 commit into
quarto-dev:mainfrom
shikokuchuo:fix-windows-libpaths
Open

Fix Windows libPaths propagation via Rscript command line#303
shikokuchuo wants to merge 1 commit into
quarto-dev:mainfrom
shikokuchuo:fix-windows-libpaths

Conversation

@shikokuchuo

Copy link
Copy Markdown
Member

Problem

On Windows, .libPaths() from the calling R session never reach the R process that executes knitr chunks, so quarto_render() fails to find packages in non-default libraries (e.g. R CMD build's temp library during vignette rebuilding). Fixes #217.

Root cause

The process chain is R session → quarto.exe → Rscript rmd.R. quarto_run() passes R_LIBS as an environment variable (via processx), which works on Unix, but on Windows the variable is not inherited across the second hop when quarto.exe (Deno) spawns Rscript. Environment variables are lost on that hop, but command-line arguments are passed reliably.

Fix

Use QUARTO_KNITR_RSCRIPT_ARGS (Quarto ≥ 1.7), which quarto.exe reads from its own environment (first hop works) and inserts into the Rscript command line:

  • New inst/rmd-init.R becomes the Rscript entry point. It restores .libPaths() from a hex-encoded trailing argument read via commandArgs(trailingOnly = TRUE), then source()s Quarto's real rmd.R (appended to the command line by Quarto CLI), so rendering proceeds exactly as before.
  • Hex encoding (of UTF-8 bytes) keeps the payload comma-free — Quarto splits the env var on , — and pure ASCII, immune to Windows quoting issues and non-ASCII path mangling.
  • R_LIBS is still set on all platforms (unchanged Unix mechanism); both channels share the same opt-out (options(quarto.use_libpaths = FALSE) / libpaths = NULL).
  • Quarto < 1.7 ignores the unknown env var, degrading gracefully to current behavior.

Note Rscript -e '...' script.R runs only the expression (the script never runs), which is why the init-script + trailing-args pattern is used rather than -e injection.

Supersedes the R_ENVIRON_USER approach in #298, which relies on the same broken env-var channel.

Tests

  • Removed skip_on_os("windows") from "quarto sees same libpaths as main process" — it should now pass on Windows CI.
  • New tests/testthat/test-libpaths.R: hex round-trip (spaces, non-ASCII), QUARTO_KNITR_RSCRIPT_ARGS construction and user-value preservation, and an end-to-end Rscript inst/rmd-init.R <hex> probe.R run.
  • Verified locally against the real Quarto CLI that rendering with the injected args restores the temp library in the rendered document's .libPaths() output.

On Windows, R_LIBS set on the quarto.exe process is not inherited by the
Rscript process that Quarto CLI spawns for the knitr engine, so session
.libPaths() never reach rendered documents (quarto-dev#217).

Pass the lib paths through the Rscript command line instead, via
QUARTO_KNITR_RSCRIPT_ARGS (Quarto >= 1.7): a new inst/rmd-init.R becomes
the Rscript entry point, restores .libPaths() from a hex-encoded trailing
argument, then sources Quarto's rmd.R. Hex encoding keeps the payload
comma-free (Quarto splits the env var on commas) and pure ASCII (immune
to Windows quoting and non-ASCII path mangling).

R_LIBS is still set on all platforms; older Quarto versions ignore the
unknown env var, so behavior there is unchanged. The previously skipped
test now runs on Windows.
@shikokuchuo shikokuchuo mentioned this pull request Aug 25, 2026
@shikokuchuo
shikokuchuo requested a review from cderv August 25, 2026 15:22

@cderv cderv left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wow thank you !

but on Windows the variable is not inherited across the second hop when quarto.exe (Deno) spawns Rscript. Environment variables are lost on that hop,

Is this is Deno or Quarto issue ? I want to understand why this happenned only in R CMD CHECK, and could it be a problem for other env var in that case ?

I really want to understand all this before merging so I'll spend some time on this.

@shikokuchuo

Copy link
Copy Markdown
Member Author

This is an R/Rscript issue AFAIU and it's appeared before in the context of targets/crew/mirai workers. There it was solved in a different way, but our solution here is the 'proper' one, if there is such a thing.

The only reference in R is a comment in base R's documentation for system2(), in the Details section:

"On Windows, env is only supported for commands such as R and make which accept environment variables on their command line."

It's a bit oblique, but 'accept on the command line' is exactly what we're doing here.

@cderv

cderv commented Aug 25, 2026

Copy link
Copy Markdown
Member

This is an R/Rscript issue AFAIU

I was also looking into this at the time. Something in how R is called during R CMD build on windows.

I shared in #217 several r-source links where they set R_LIBS using env var
https://github.com/wch/r-source/blob/f50aab97c98615643db276bb034701816ab43143/src/library/tools/R/build.R#L293-L300

and then they use system2() to call Rscript

https://github.com/wch/r-source/blob/f50aab97c98615643db276bb034701816ab43143/src/library/tools/R/build.R#L312

https://github.com/wch/r-source/blob/f50aab97c98615643db276bb034701816ab43143/src/library/tools/R/build.R#L109-L116

And I believe this is where it could not be working...

The only reference in R is a comment in base R's documentation for system2(), in the Details section

but they don't use env= - this is not used. Sys.setenv() is used but somehow process in not passed.

I would need to retry locally and verify if I can reproduce using baseR or if this is something only on CI.

You had the problem different than #217 ? or you are trying to solve 217 ?

@shikokuchuo

Copy link
Copy Markdown
Member Author

I ran into #217 some months ago, which motivated the fix. I'm using this argv parsing pattern elsewhere, which is why I knew it'd be good here. There is some general difficulty with child processes inheriting env vars on Windows, but I forget which is the essential part - if it's system2() or something else - and I don't have a working Windows machine to test on...

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.

Windows CI Errors on Package Re-build

2 participants