Fix terminal launcher packaging with one Tauri executable - #354
Merged
Conversation
shunichironomura
force-pushed
the
hotfix-terminal-launchers
branch
from
July 21, 2026 14:42
f07b662 to
20686c6
Compare
Tauri patches the main executable with its package type before building each installer, so the NSIS and MSI copies always differ by the NSS/MSI marker. Canonicalize that marker to UNK before hashing so the equivalence check still proves both installers ship the same compiled GUI.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This is the v0.0.5 hotfix for the Windows
TaskDialogIndirectstartup failure introduced by the v0.0.4 terminal command.The fix separates the two responsibilities that v0.0.4 accidentally combined:
procnote-tauriis the only Tauri application. It owns the window, plugins, Windows manifest, resources, subsystem, and GUI runtime configuration.procnote-launcheris a small, Tauri-free console executable. It owns the publicprocnote [WORKSPACE]command, prints help/version/errors synchronously, and starts the packaged GUI asynchronously.The release version is bumped to 0.0.5 because v0.0.4 already has published artifacts.
Testing the package on a separate Windows machine also exposed an existing persistence bug: the launcher worked, but starting an execution failed with
Access is denied. (os error 5). This PR now fixes that Windows directory-sync failure as well.Why v0.0.4 fails on Windows
v0.0.4 built two executables from different Cargo packages:
procnote-tauri, built by the normal Tauri packaging flow; andprocnote-cli, built separately withcargo buildand copied intocli/procnote.exe.Although the second package called into the Tauri library, Cargo build-script output is package-scoped. Settings emitted by
src-tauri/build.rsforprocnote-tauriwere therefore not automatically applied toprocnote-cli.That distinction is critical on Windows:
tauri-plugin-dialogreachesrfd, which importsTaskDialogIndirect.TaskDialogIndirectrequires the Common Controls v6 activation context selected by an application manifest.TaskDialogIndirectcould not be found.The separate build also bypassed other package-scoped linker and runtime settings. Adding a one-off manifest to the old CLI would fix only the visible symptom and preserve the same design trap.
Native launcher architecture
A real console frontend, not a second Tauri app
crates/procnote-launcherdepends on Clap,thiserror, and small platform APIs. It deliberately does not depend onprocnote-tauri, Tauri,rfd, or any GUI/dialog library.This means two native executables are installed, but only one is a Tauri application:
This directly prevents the original
TaskDialogIndirectdependency from entering the launcher.Public command behavior
Clap runs in the console launcher, so terminal behavior is deterministic:
procnote --helpprints usage and exits successfully.procnote --versionprintsprocnote 0.0.5and exits successfully.procnote [WORKSPACE]starts the GUI and returns immediately.-remains usable throughprocnote -- <workspace>.The launcher passes
--and the workspace as separate OS-native arguments to the GUI. It never constructs a shell command, so spaces and shell metacharacters do not change argument boundaries.Process behavior
std::process::Commandinherits the caller's working directory and environment by default. The launcher intentionally leaves both unchanged, allowing the GUI to canonicalize relative paths such as.in the terminal's original context.Only GUI launch actions are detached:
setsid, receives null standard streams, and starts in a new session without a controlling terminal.DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUPand null standard streams.Help, version, and parser errors never reach the spawn path.
Package layouts
Windows
The NSIS installer adds
binto the current user's PATH. The MSI contains the same launcher but currently requires manual PATH configuration.macOS
The launcher resolves Homebrew/manual symlinks before locating the GUI. The Homebrew cask links the console launcher rather than the GUI executable.
Linux
Debian and RPM package mappings install the launcher automatically. AppImage setup remains manual.
Reproducible launcher packaging
scripts/prepare-launcher.mjsuses Tauri's target environment to:procnote-launcherfor the same target triple/profile as the package;cargo metadata;src-tauri/launchers/bin/; andGenerated binary paths cannot live in Tauri's automatically loaded platform configuration:
tauri-buildvalidates resources during direct Cargo builds, before a generated launcher exists. To keep clean-checkoutcargo checkandcargo testworking, launcher mappings live in explicittauri.bundle.*.conf.jsonextensions.scripts/tauri.mjswraps the local Tauri CLI and injects the correct extension for desktopbuildcommands, including cross-architecture macOS builds. The release action explicitly uses this wrapper throughtauriScript: "pnpm run tauri". User-provided Tauri config extensions remain later in merge order.The ordinary automatically loaded Linux config retains only
mainBinaryName: procnote-gui; direct Rust checks therefore do not depend on generated package artifacts.Safe Windows PATH migration
The old NSIS hook read and rewrote PATH using NSIS strings. Standard NSIS strings are limited to 1024 characters, so a sufficiently long user PATH could be truncated during installation or uninstallation.
The new hook delegates the registry transformation to Windows PowerShell and the .NET registry API. The helper:
%USERPROFILE%;REG_SZorREG_EXPAND_SZ;$INSTDIR\binand legacy$INSTDIR\clientry case-insensitively;$INSTDIR\bin-tools;$INSTDIR\binentry on installation;WM_SETTINGCHANGEafter the update.During a v0.0.4 upgrade, the NSIS hook also removes the obsolete
$INSTDIR\clidirectory. This matters because Windows normally resolves.exebefore.cmdthroughPATHEXT; leaving the legacy executable available could continue selecting the broken binary.Windows runtime configuration
Windows MSVC builds enable Tauri's static versioned VC-runtime support and Cargo's
crt-statictarget feature. The setting also applies to the new launcher.The executables may dynamically use UCRT (
UCRTBASE.DLL/API-MS-WIN-CRT-*), which is a Windows component, but artifact validation rejects dependencies on versioned Visual C++ Redistributable libraries such asVCRUNTIME,MSVCP, andCONCRT.Windows execution persistence
A real-machine smoke test confirmed that the packaged launcher,
--help, and--versionworked, but clicking Start on a procedure returnedAccess is denied. (os error 5). The failed attempts left an empty.executionsdirectory, while an independent write probe in the same procedure directory succeeded. That isolated the error to the durability step immediately after directory creation rather than workspace permissions, Controlled Folder Access, or the launcher.ExecutionStore::create_execution()creates<procedure>/.executionsand then callssync_dir(). The previous Windows implementation opened that directory read-only withFILE_FLAG_BACKUP_SEMANTICSand calledFile::sync_all(). Rust implements that call withFlushFileBufferson Windows; the API requires a write-capable handle and does not provide POSIX directory-fsyncsemantics, so the read-only directory handle returnedERROR_ACCESS_DENIED.The Windows
sync_dir()implementation is now intentionally a no-op. Unix platforms retain directoryfsync, while actual data files—including event logs, execution snapshots, and attachments—continue to callFile::sync_all()at their durable write points on every platform. Existing empty.executionsdirectories are harmless and can be reused.A focused
syncing_existing_directory_succeedsregression test covers the helper. The Rust CI workflow now runs the persistence tests on nativewindows-latest, ensuring the original implementation would fail CI rather than reaching package testing again.Artifact validation
The release workflow removes cached
bundledirectories before packaging so validators cannot accidentally inspect a stale artifact.macOS
The validator:
--helpand--versionoutput when host-compatible;Both the arm64 build and x86_64 staging path are architecture-aware.
Linux
The validator:
/usr/bin/procnoteand/usr/bin/procnote-gui;procnote-gui.Windows
The native Windows validator inspects both NSIS and MSI payloads. It verifies:
clipayload remains;Microsoft.Windows.Common-Controlsv6;COMCTL32.DLL;--versionpath; andIt then exercises the real installer lifecycle:
procnotetobin\procnote.exe, and capture its version output;finallyblock.The MSI is inspected through an administrative extraction, validating its payload without a normal machine installation.
Distribution behavior
.deb/ RPM/usr/bin/procnoteinstalled automaticallyCompatibility
This change does not alter procedure templates, execution state, or the append-only event-log schema. Windows no longer attempts the unsupported POSIX-style parent-directory sync; synchronization of actual event logs, snapshots, and attachments is unchanged.
Validation performed
just lintsrc/lib/index.tsempty-file warning.cargo test --workspacepassed from a clean launcher-staging state.-- <workspace>contract.windows-latestpersistence-test job passed and is required by the aggregate Rust CI check.cargo check --package procnote-launcher --target x86_64-pc-windows-msvcpassed.lipo.The native Windows persistence regression job now passes. The remaining release gate is the full NSIS/MSI lifecycle followed by repeating the packaged Start execution smoke test with the rebuilt installer. The v0.0.5 draft should not be published until those checks pass.
Suggested review order
crates/procnote-launcher/for the public CLI and process model.src-tauri/src/persistence/event_log.rsfor the Windows directory-sync fix.src-tauri/launchers/README.mdfor package architecture and invariants.scripts/prepare-launcher.mjs,scripts/tauri.mjs, andtauri.bundle.*.conf.jsonfor reproducible staging.src-tauri/nsis/hooks.nshandupdate-user-path.ps1for Windows migration..github/workflows/*.yamlandscripts/validate-*-package.*for CI and release safeguards.