Summary
vscode-R's session watcher attaches via R/session/init.R, which assigns a .First.sys shadow into the global environment during profile loading and relies on R's startup invoking it after default packages load (init_first → assign(".First.sys", init_last, envir = globalenv())). On R 4.6.x, R's startup no longer calls a globalenv .First.sys override — the shadow is armed but never invoked — so the watcher never attaches, options(device=...) is never set, and every plot() in the R terminal silently writes Rplots.pdf. No error appears anywhere, which makes this brutal to diagnose in teaching environments.
Verified empirically with a minimal profile (no extension involved):
cat > /tmp/profile.R <<'PROF'
first_sys_orig <- .First.sys
.First.sys <- function() {
first_sys_orig()
writeLines("fired", "/tmp/fired")
}
PROF
rm -f /tmp/fired
R_PROFILE_USER=/tmp/profile.R R -q --no-save -e 'invisible(NULL)'
ls /tmp/fired
- R 4.5.3 (macOS): marker created — shadow honored.
- R 4.6.1 (Linux, rocker image): marker never created — shadow bypassed.
Same result across consoles (plain R and custom front ends alike), since the change is in R's own startup, not any front end. We could not find the change documented in the R 4.6.0/4.6.1 NEWS; it may be a side effect of internal startup refactoring.
Observed in the wild
vscode-R 2.8.8 + GitHub Codespaces + R 4.6.1: after startup, exists(".First.sys", envir=globalenv(), inherits=FALSE) is TRUE (armed, unfired), "tools:vscode" %in% search() is FALSE, plot() → Rplots.pdf. Manually calling .First.sys() attaches the watcher and everything works from then on — only the startup invocation is missing.
Workaround we ship (site profile)
.First <- function() {
fs <- globalenv()$.First.sys
if (is.function(fs)) fs()
}
R still honors the documented .First() user hook after all profiles, so this fires the armed shadow; it's a no-op outside vscode-R sessions. Works on R 4.6.1 (CI-tested). The extension could adopt something equivalent in profile.R/init.R (e.g., arrange the deferred init via .First, or a top-level task callback) rather than relying on the .First.sys override that R >= 4.6 ignores.
Happy to test a fix — we ship vscode-R to a large intro data-science course.
Summary
vscode-R's session watcher attaches via
R/session/init.R, which assigns a.First.sysshadow into the global environment during profile loading and relies on R's startup invoking it after default packages load (init_first→assign(".First.sys", init_last, envir = globalenv())). On R 4.6.x, R's startup no longer calls a globalenv.First.sysoverride — the shadow is armed but never invoked — so the watcher never attaches,options(device=...)is never set, and everyplot()in the R terminal silently writesRplots.pdf. No error appears anywhere, which makes this brutal to diagnose in teaching environments.Verified empirically with a minimal profile (no extension involved):
Same result across consoles (plain R and custom front ends alike), since the change is in R's own startup, not any front end. We could not find the change documented in the R 4.6.0/4.6.1 NEWS; it may be a side effect of internal startup refactoring.
Observed in the wild
vscode-R 2.8.8 + GitHub Codespaces + R 4.6.1: after startup,
exists(".First.sys", envir=globalenv(), inherits=FALSE)isTRUE(armed, unfired),"tools:vscode" %in% search()isFALSE,plot()→Rplots.pdf. Manually calling.First.sys()attaches the watcher and everything works from then on — only the startup invocation is missing.Workaround we ship (site profile)
R still honors the documented
.First()user hook after all profiles, so this fires the armed shadow; it's a no-op outside vscode-R sessions. Works on R 4.6.1 (CI-tested). The extension could adopt something equivalent inprofile.R/init.R(e.g., arrange the deferred init via.First, or a top-level task callback) rather than relying on the.First.sysoverride that R >= 4.6 ignores.Happy to test a fix — we ship vscode-R to a large intro data-science course.