Windows: 32-bit (multiarch) session binaries are missing from the installer
envgap__rstudio__rstudio-18515
01 / FAILURE SIGNATURE
As reported upstream
generic "R session had a fatal error" dialog with nothing naming the real cause. That last
Not a benchmark task.
- In a clean container the reported failure did not reproduce, or the known fix did not make the project run.
02 / ENVIRONMENT RECIPE
- Base commit
06473dc67a10e6195f0afff216c84d235958ae32- Manifest
src/cpp/session/CMakeLists.txt- Reproduce
Awaiting issue-specific recipe- Run under trace
Awaiting a meaningful runtime command
03 / ORIGINAL ISSUE TEXT
rstudio/rstudio #18515 · read the original issue
## Problem
Multiarch Windows Desktop builds build, install and code-sign the 32-bit session, then
drop it before packaging. In the shipped installer, `resources/app/bin/x86` contains only
the x86 VC runtime DLLs installed by `src/node/desktop/CMakeLists.txt:320` -- no
`rsession.exe`, and (since #18070) no `rsession.dll`.
Evidence from a local multiarch package build:
- `package\win32\build\src\cpp\session\x86\` -- `rsession.exe`, `rsession.dll`
- `<CPack staging>\resources\app\bin\x86\` -- 9 VC runtime DLLs, no session binaries
(same in both the `NSIS` and `ZIP` staging trees)
## How this happened
`04db3f557da` (#17814, 2026-06-02, "Migrate the Windows terminal backend from winpty to
ConPTY") deleted the whole `if(WIN32)` block under `# install winpty on windows` in
`src/cpp/session/CMakeLists.txt`. Four of the six install rules in that block were
winpty's; the other two were not. The rule that collected the 32-bit session went with it
(`v2026.05.1+225:src/cpp/session/CMakeLists.txt:856-860`):
```cmake
# install 32 bit binaries
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/x86")
install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/x86"
USE_SOURCE_PERMISSIONS
DESTINATION ${RSTUDIO_INSTALL_BIN})
```
`git log -S'install 32 bit binaries' -- src/cpp/session/CMakeLists.txt` returns exactly two
commits: `e505193f677` (2019) added the rule, `04db3f557da` removed it.
## Mechanism
The 32-bit sub-build has always installed into the 64-bit *build* tree; the 64-bit build
then had an install rule that scooped that directory into the staged app tree at package
time.
- `package/win32/make-package.bat:242-247` runs the 32-bit sub-build after the 64-bit
build, via `make-install-win32.bat "%BUILD_DIR%\src\cpp\session"`.
- `package/win32/make-install-win32.bat:55-66` configures `build32` with that path as
`CMAKE_INSTALL_PREFIX` and `RSTUDIO_TARGET=SessionWin32`. `RSTUDIO_ELECTRON` is false for
that target, so `RSTUDIO_INSTALL_BIN` is plain `x86` (`cmake/globals.cmake:521-525`),
giving `build/src/cpp/session/x86/`.
- In the 64-bit Electron build, `RSTUDIO_INSTALL_BIN` is `resources/app/bin`
(`cmake/globals.cmake:512-517`) and the session project's `CMAKE_CURRENT_BINARY_DIR` is
`build/src/cpp/session`, so the deleted rule copied that directory to
`resources/app/bin/x86`. `install(DIRECTORY)` without a trailing slash installs the
directory itself, hence the `/x86` suffix. CPack re-runs install rules at packaging
time -- after the sub-build -- so the directory was populated by then.
Nothing replaced the rule, and no other rule in the tree collects that directory.
## Impact
`src/node/desktop/src/main/session-launcher.ts:654-663` is the only consumer:
```ts
const arch = getenv('R_ARCH');
if (arch === 'i386') {
const x86SessionPath = this.sessionPath.getParent().completeChildPath('x86/rsession.exe');
if (x86SessionPath.existsSync()) {
this.sessionPath = x86SessionPath;
...
} else {
logger().logWarning('R is 32-bit, but no 32-bit rsession.exe was found');
}
}
```
`R_ARCH` comes from `.Platform$r_arch`, queried out of R itself
(`src/node/desktop/src/main/detect-r.ts:75`), so this is auto-detected, not gated on a
preference -- `platform.windows.useDefault32BitR` and the Choose R dialog only select which
R. With a 32-bit R, the lookup fails, a warning is logged, and the launcher falls through
to the 64-bit `rsession.exe`, which cannot load a 32-bit `R.dll`; the user should get the
generic "R session had a fatal error" dialog with nothing naming the real cause. That last
step is inferred from the code path and has not been reproduced against a built installer.
Affected releases (verified with `git merge-base --is-ancestor`; #18070 split the session
into `rsession.dll` plus launcher stubs from 2026.07 onward):
| Release | Missing payload |
| --- | --- |
| `v2026.05.1+225` and earlier | -- (working) |
| `v2026.06.0+242` | `rsession.exe` |
| `v2026.07.0+139`, `v2026.07.1+147`, current `main` | `rsession.exe` + `rsession.dll` |
Two related things are dead while this is broken: `src/cpp/session/SessionOptions.cpp:123-128`,
which strips a trailing `x86` component from the resource path for the 32-bit session; and
the `x86\rsession.exe` entry in the `Sign Executables` stage of `jenkins/Jenkinsfile.windows`
(plus `x86\rsession.dll` once #18512 lands), which signs files that never ship.
## Fix
Restore the collection rule in `src/cpp/session/CMakeLists.txt`, at roughly line 911 --
immediately before the `# install libclang` block -- inside the existing
`if(NOT RSTUDIO_SESSION_WIN32 AND NOT RSESSION_ALTERNATE_BUILD)` guard opened at line 739:
```cmake
# collect the 32-bit session binaries produced by the multiarch sub-build, which
# package/win32/make-install-win32.bat installs into <build>/src/cpp/session/x86
if(WIN32)
file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/x86")
install(DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/x86"
USE_SOURCE_PERMISSIONS
DESTINATION "${RSTUDIO_INSTALL_BIN}")
endif()
```
Keep the comment tying the rule to `make-install-win32.bat`: the rule was lost precisely
because it sat under a `# install winpty on windows` comment with nothing indicating it
was unrelated to winpty.
`file(MAKE_DIRECTORY)` is load-bearing, not vestigial. `install(DIRECTORY)` errors on a
missing source directory, so without it every non-multiarch Windows build fails at install
-- and `MULTIARCH` defaults to 1 only on Jenkins (`package/win32/make-package.bat:43-48`),
so local builds are the common case.
Prefer this over repointing the sub-build's install prefix at the staged app tree. That
tree is CPack-owned and only exists during packaging, and signing runs against the build
tree between the sub-build and CPack (`jenkins/Jenkinsfile.windows`), so the payload has to
be collected at package time for signed binaries to ship.
Verification for the fix:
- multiarch package build (`make-package.bat clean electron multiarch`): `rsession.exe` and
`rsession.dll` present in `<staging>\resources\app\bin\x86`
- non-multiarch build: still configures and installs cleanly, with an empty `x86` directory
- ideally, a smoke test with a 32-bit R selected, confirming the session starts
Needs a `NEWS.md` entry under `### Fixed` (the regression shipped in 2026.06.0, so it has
reached official-release users).
04 / LABELS
Labels from the report text only; not yet run
No supported category has been assigned.
Label rules and the text that matched
[]