Fixes #755.
Problem
On Unix-likes, R's own front-end ($(R RHOME)/bin/R) is a POSIX shell script. Its -e/-f/--file= argument handling reuses a plain, unexported local shell variable literally named a to hold the encoded expression text before it execs into the real R binary:
-e)
a=`(echo "${2}" && echo) | sed -e 's/ /~+~/g' | ...`
args="${args} -e $a"
;;
...
exec "${R_binary}" ${args} "${@}"
Because POSIX shells retain a variable's exported attribute once it's inherited from the environment, if the shell was launched with an env var already named a (e.g. a user-set container/notebook env var), this plain reassignment silently overwrites it with the encoded -e text — and the clobbered value is inherited by the real R process via the trailing exec, for the entire lifetime of that R session.
IRkernel's generated kernelspec launches R via ["R", "--slave", "-e", "IRkernel::main()", "--args", "{connection_file}"] — exactly this vulnerable code path. So any Jupyter user with an environment variable literally named a gets it permanently corrupted the moment their kernel starts: Sys.getenv("a") returns "IRkernel::main()" instead of the real value. Renaming to A, b, etc. is unaffected. See #755 for the full root-cause writeup and repro.
Fix
On Unix-likes, installspec() now generates a kernelspec that launches via a small sh -c wrapper piping IRkernel::main() into R's stdin, instead of passing it via -e:
echo 'IRkernel::main()' | exec /path/to/R --slave --args "$0"
This sidesteps the -e/-f/--file= code path in R's front-end entirely, so the a-variable collision never happens. Windows is untouched — R's front-end there is a compiled executable rather than a shell script, so it isn't subject to this bug, and there's no sh on a plain Windows R install to run the wrapper anyway.
Testing
Verified locally against a real R install (Alpine R=4.6.0-r0, though the front-end script logic is version/platform-independent for Unix-likes):
$ docker run --rm -e a=a -e A=A -e b=b r-base:latest \
R --slave -e 'print(Sys.getenv("a")); print(Sys.getenv("A")); print(Sys.getenv("b"))' --args /dev/null
[1] "print(Sys.getenv(\"a\"));~+~print(Sys.getenv(\"A\"));~+~print(Sys.getenv(\"b\"))" # <- bug
[1] "A"
[1] "b"
$ docker run --rm -e a=a -e A=A -e b=b r-base:latest sh -c \
'echo "cat(Sys.getenv(\"a\"),Sys.getenv(\"A\"),Sys.getenv(\"b\"))" | exec R --slave --args /dev/null'
a A b # <- fixed
Also confirmed the exact sh -c string this patch's sprintf()/shQuote() call generates for a real installspec() call, and ran it end-to-end with the same env vars set, with the same passing result.
I'm aware this project's had a quiet stretch, looks like three years or so. I'm happy to adjust the approach if maintainers have a preferred direction, or if there's a reason this hasn't been a priority -- already have a workaround in our own platform. But, I wanted to file this as a self-contained, backward-compatible fix in case it's useful.
🤖 PR description generated with Claude Code