Tauri v2 NSIS Installer: externalBin Sidecar Not Replaced on Reinstall
Summary
When using Tauri v2's externalBin feature with NSIS installers, the sidecar binary may not be replaced during reinstalls or upgrades. This leads to the installed application running a stale sidecar while the main Tauri app binary is correctly updated. The behavior is silent — no errors are reported during install — making it difficult to diagnose.
Environment
- Tauri: v2 (CLI and bundler)
- Platform: Windows 11 (x64)
- Installer: NSIS (via
bundle.targets: ["nsis"])
- Sidecar: PyInstaller-built Python exe (~71MB), configured via
bundle.externalBin
Configuration
{
"bundle": {
"active": true,
"targets": ["nsis"],
"externalBin": ["quote-sidecar"]
}
}
The sidecar binary is placed at:
src-tauri/quote-sidecar-x86_64-pc-windows-msvc.exe
The Problem
Issue 1: Stale sidecar bundled from wrong path
What happened: After rebuilding the sidecar binary and running tauri build, the NSIS installer continued to bundle the old sidecar. The main Tauri app (Rust binary) was correctly rebuilt, but the sidecar inside the installer was stale.
Root cause: Tauri copies the sidecar to src-tauri/target/release/quote-sidecar.exe (stripping the target triple) during the first build. On subsequent builds, if this cached copy exists and the build system doesn't detect the source has changed, it reuses the stale cached copy.
Evidence: The generated NSIS script (target/release/nsis/x64/installer.nsi) correctly references src-tauri/quote-sidecar-x86_64-pc-windows-msvc.exe — but by the time NSIS runs, Tauri's bundler has already resolved the binary path. In our case, a stale 188MB binary persisted while the rebuilt binary was 71MB.
Fix: We had to manually ensure the file at src-tauri/quote-sidecar-{target-triple}.exe was the current build. A binaries/ subdirectory we had been using was not the path Tauri resolves — externalBin resolves relative to src-tauri/, not src-tauri/binaries/.
Issue 2: NSIS installer doesn't replace sidecar on same-version reinstall
What happened: Even when the correct sidecar was bundled in the installer, reinstalling the same version (0.1.0 → 0.1.0) did not overwrite the installed sidecar binary. The main app exe was replaced, but the sidecar was left untouched.
Root cause: NSIS file replacement behavior during same-version installs. The sidecar exe has no Windows version resource (it's a PyInstaller-built binary), so NSIS's version comparison logic may skip it. Additionally, if the sidecar process is still running when the installer copies files, the copy silently fails.
Partial mitigation: We added NSIS installer hooks to kill the running sidecar before install:
; src-tauri/installer-hooks.nsh
!macro NSIS_HOOK_PREINSTALL
nsExec::Exec 'taskkill /F /IM quote-sidecar.exe'
nsExec::Exec 'taskkill /F /IM coda-quote.exe'
Sleep 1000
Delete "$INSTDIR\quote-sidecar.exe"
!macroend
{
"bundle": {
"windows": {
"nsis": {
"installerHooks": "installer-hooks.nsh"
}
}
}
}
This helps with the "file in use" problem but doesn't fully solve the version-comparison issue.
Issue 3: CSP blocks sidecar API in production builds
What happened: The sidecar API (http://127.0.0.1:8000) worked fine in dev mode but returned no data in the installed production app. No errors were visible.
Root cause: In dev mode, the frontend runs on http://localhost:1420. In production, Tauri serves from https://tauri.localhost. The default CSP allowed connect-src to http://127.0.0.1:8000, but the production origin (https://tauri.localhost) treating requests to http:// as mixed content.
Fix: Added https://tauri.localhost to CSP and dangerousDisableAssetCspModification:
{
"app": {
"security": {
"csp": "default-src 'self'; connect-src 'self' http://localhost:8000 http://127.0.0.1:8000 https://tauri.localhost; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'",
"dangerousDisableAssetCspModification": true
}
}
}
Documentation Gaps
-
externalBin path resolution is not clearly documented. The docs don't explicitly state that externalBin: ["quote-sidecar"] resolves to src-tauri/quote-sidecar-{target-triple}.exe. Users may assume it resolves from a binaries/ subdirectory (which some Tauri v1 examples showed).
-
No mention of sidecar caching in target/release/. When rebuilding a sidecar outside of Cargo (e.g., PyInstaller, Go, etc.), the cached copy in target/release/ is not automatically invalidated. The docs should warn about this.
-
NSIS reinstall behavior with unversioned binaries is not documented. The docs describe installerHooks but don't explain that sidecars without Windows version resources may not be replaced on reinstall.
-
CSP for production sidecar communication is not covered in the sidecar documentation. The dev → production origin change (http://localhost → https://tauri.localhost) is a common gotcha for sidecar architectures.
Recommendations for Tauri
For the build system:
- Always re-copy
externalBin files to target/release/ before bundling, comparing checksums rather than relying on timestamp/existence checks.
- Log the sidecar path and size during
tauri build so developers can verify the correct binary is being bundled.
- Warn if
target/release/{sidecar}.exe is older than src-tauri/{sidecar}-{triple}.exe.
For NSIS installer:
- Default to
SetOverwrite on for sidecar binaries, or always delete-then-copy to handle unversioned executables.
- Include process termination for known sidecar binaries in the default NSIS template (pre-install hook).
For documentation:
- Explicitly document
externalBin path resolution: src-tauri/{name}-{target-triple}{.exe}.
- Add a "Rebuilding Sidecars" section covering the
target/release/ cache issue for non-Cargo sidecars.
- Add CSP guidance for sidecar communication in production builds.
- Document
installerHooks with a practical sidecar example (kill process, delete old binary).
What Finally Worked
- Place sidecar at
src-tauri/quote-sidecar-x86_64-pc-windows-msvc.exe (not in a subdirectory)
- Use
--clean flag with PyInstaller and clear __pycache__ before building
- Use NSIS
installerHooks to kill running processes and delete the old binary pre-install
- Add
dangerousDisableAssetCspModification: true and https://tauri.localhost to CSP
- Bump the version number when making sidecar changes to force NSIS to treat it as an upgrade
- Add
npm run build:sidecar script to standardize the sidecar build with correct output path
Build Commands (Working)
# 1. Build the sidecar (outputs to src-tauri/)
npm run build:sidecar
# 2. Build the Tauri app + installer
npm run tauri:build
# Installer output:
# src-tauri/target/release/bundle/nsis/
Tauri v2 NSIS Installer: externalBin Sidecar Not Replaced on Reinstall
Summary
When using Tauri v2's
externalBinfeature with NSIS installers, the sidecar binary may not be replaced during reinstalls or upgrades. This leads to the installed application running a stale sidecar while the main Tauri app binary is correctly updated. The behavior is silent — no errors are reported during install — making it difficult to diagnose.Environment
bundle.targets: ["nsis"])bundle.externalBinConfiguration
{ "bundle": { "active": true, "targets": ["nsis"], "externalBin": ["quote-sidecar"] } }The sidecar binary is placed at:
The Problem
Issue 1: Stale sidecar bundled from wrong path
What happened: After rebuilding the sidecar binary and running
tauri build, the NSIS installer continued to bundle the old sidecar. The main Tauri app (Rust binary) was correctly rebuilt, but the sidecar inside the installer was stale.Root cause: Tauri copies the sidecar to
src-tauri/target/release/quote-sidecar.exe(stripping the target triple) during the first build. On subsequent builds, if this cached copy exists and the build system doesn't detect the source has changed, it reuses the stale cached copy.Evidence: The generated NSIS script (
target/release/nsis/x64/installer.nsi) correctly referencessrc-tauri/quote-sidecar-x86_64-pc-windows-msvc.exe— but by the time NSIS runs, Tauri's bundler has already resolved the binary path. In our case, a stale 188MB binary persisted while the rebuilt binary was 71MB.Fix: We had to manually ensure the file at
src-tauri/quote-sidecar-{target-triple}.exewas the current build. Abinaries/subdirectory we had been using was not the path Tauri resolves —externalBinresolves relative tosrc-tauri/, notsrc-tauri/binaries/.Issue 2: NSIS installer doesn't replace sidecar on same-version reinstall
What happened: Even when the correct sidecar was bundled in the installer, reinstalling the same version (
0.1.0→0.1.0) did not overwrite the installed sidecar binary. The main app exe was replaced, but the sidecar was left untouched.Root cause: NSIS file replacement behavior during same-version installs. The sidecar exe has no Windows version resource (it's a PyInstaller-built binary), so NSIS's version comparison logic may skip it. Additionally, if the sidecar process is still running when the installer copies files, the copy silently fails.
Partial mitigation: We added NSIS installer hooks to kill the running sidecar before install:
{ "bundle": { "windows": { "nsis": { "installerHooks": "installer-hooks.nsh" } } } }This helps with the "file in use" problem but doesn't fully solve the version-comparison issue.
Issue 3: CSP blocks sidecar API in production builds
What happened: The sidecar API (
http://127.0.0.1:8000) worked fine in dev mode but returned no data in the installed production app. No errors were visible.Root cause: In dev mode, the frontend runs on
http://localhost:1420. In production, Tauri serves fromhttps://tauri.localhost. The default CSP allowedconnect-srctohttp://127.0.0.1:8000, but the production origin (https://tauri.localhost) treating requests tohttp://as mixed content.Fix: Added
https://tauri.localhostto CSP anddangerousDisableAssetCspModification:{ "app": { "security": { "csp": "default-src 'self'; connect-src 'self' http://localhost:8000 http://127.0.0.1:8000 https://tauri.localhost; script-src 'self' 'unsafe-eval'; style-src 'self' 'unsafe-inline'", "dangerousDisableAssetCspModification": true } } }Documentation Gaps
externalBinpath resolution is not clearly documented. The docs don't explicitly state thatexternalBin: ["quote-sidecar"]resolves tosrc-tauri/quote-sidecar-{target-triple}.exe. Users may assume it resolves from abinaries/subdirectory (which some Tauri v1 examples showed).No mention of sidecar caching in
target/release/. When rebuilding a sidecar outside of Cargo (e.g., PyInstaller, Go, etc.), the cached copy intarget/release/is not automatically invalidated. The docs should warn about this.NSIS reinstall behavior with unversioned binaries is not documented. The docs describe
installerHooksbut don't explain that sidecars without Windows version resources may not be replaced on reinstall.CSP for production sidecar communication is not covered in the sidecar documentation. The dev → production origin change (
http://localhost→https://tauri.localhost) is a common gotcha for sidecar architectures.Recommendations for Tauri
For the build system:
externalBinfiles totarget/release/before bundling, comparing checksums rather than relying on timestamp/existence checks.tauri buildso developers can verify the correct binary is being bundled.target/release/{sidecar}.exeis older thansrc-tauri/{sidecar}-{triple}.exe.For NSIS installer:
SetOverwrite onfor sidecar binaries, or always delete-then-copy to handle unversioned executables.For documentation:
externalBinpath resolution:src-tauri/{name}-{target-triple}{.exe}.target/release/cache issue for non-Cargo sidecars.installerHookswith a practical sidecar example (kill process, delete old binary).What Finally Worked
src-tauri/quote-sidecar-x86_64-pc-windows-msvc.exe(not in a subdirectory)--cleanflag with PyInstaller and clear__pycache__before buildinginstallerHooksto kill running processes and delete the old binary pre-installdangerousDisableAssetCspModification: trueandhttps://tauri.localhostto CSPnpm run build:sidecarscript to standardize the sidecar build with correct output pathBuild Commands (Working)