Linux updater helper fails across filesystems (EXDEV), strands user with no working binary
Wails version family
v3
Exact Wails version
v3.0.0-beta.22
Operating System
Linux
Description
This is a similar issue to the Windows fix (https://github.com/wailsapp/wails/pull/5560).
On Linux, the v3 updater helper stages the verified artifact under
os.TempDir() (typically /tmp/wails-update-*/) and then calls bare
os.Rename(newPath, target) in helper_unix.go:replaceTarget. When /tmp
and the install directory live on different filesystems — tmpfs /tmp plus
persistent $HOME is the Arch/Fedora default — every swap attempt fails with
EXDEV (invalid cross-device link). Worse than a failed update, the recovery
path then removed the working binary while failing to restore it, leaving only
Application.bak behind and nothing running.
Windows already handles this exact failure: PR #5560 added a rename-or-copy
fallback plus rollback to helper_windows.go. Linux (helper_unix.go) was not
changed and still uses bare rename.
My Filesystems: /tmp on tmpfs, $HOME on btrfs
(df -T /tmp ~ shows two different types/mounts)
helper start: target=$HOME/Applications/Application new=/tmp/wails-update-/Application-linux-amd64 pid=... backing up $HOME/Applications/Application → $HOME/Applications/Application.bak replace (attempt 1): rename /tmp/wails-update-/Application-linux-amd64 $HOME/Applications/Application: invalid cross-device link ...repeated identically through all 20 attempts...
End state observed on disk: only `Application.bak` remained; no working `Application`
binary and no running app.
### To Reproduce
1. Use a Linux system where `/tmp` and the install directory are on different
filesystems (e.g. Arch/Fedora defaults: tmpfs `/tmp`, ext4/btrfs `$HOME`;
confirm with `df -T /tmp ~`).
2. Build any Wails v3 Linux desktop app with `app.Updater` configured and a
working provider, installed to a user-writable directory such as
`~/Applications/`.
3. Publish a signed newer release the updater accepts (digest + signature
verify cleanly, so the flow reaches the swap stage).
4. Trigger the update and restart into the helper.
5. Observe twenty `invalid cross-device link` attempts in
`/tmp/wails-update-*.log`, ending with only the `.bak` file present and no
running application.
### Expected behaviour
- The Linux helper detects the cross-device case and falls back to copy +
fsync + delete-source, mirroring the merged Windows fix in PR #5560
(`renameOrCopy`, mode preservation, cleanup, rollback that cannot strand
the user).
- Alternatively, stage the artifact beside the target (same filesystem) so
the atomic rename path holds on every layout.
- At minimum, the recovery path must never delete the working binary and
then fail to put something runnable back: verify the replacement exists
before removing the original, or copy the backup aside before destructive
operations.
### Screenshots
_No response_
### Attempted Fixes
## Suggested Fix
- Port the `renameOrCopy` fallback from `helper_windows.go` (PR #5560) to
`helper_unix.go`, keyed on EXDEV (`errno 18` on Linux) rather than on every
error, preserving the existing retry/backoff and rollback semantics.
- Consider staging the download beside the target directory when it is
writable, so the common case stays a single atomic rename.
- Add coverage for the split-mount layout (e.g. helper tests with staging
and target on different filesystems, or a fault-injected `replaceTarget`),
plus an assertion that a failed swap never leaves the install directory
without a runnable binary.
## App-Side Workaround I Shipped
Because the destructive path lives in framework code, Application now probes
the exact move at startup — rename a scratch file from `os.TempDir()` into
the executable directory, then remove it — and disables update offers with a
log line when the probe fails. That prevents strandings but leaves split-mount
users on manual tarball reinstalls; it is not a substitute for the framework
fallback above.
### System Details
```shell
Wails v3.0.0-beta.22 › Wails Doctor
# System
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
| Name | Omarchy |
| Version | 4.0.4 |
| ID | omarchy |
| Branding | |
| Platform | linux |
| Architecture | amd64 |
| Desktop Environment | Hyprland |
| NVIDIA Driver | N/A |
| XDG_SESSION_TYPE | wayland |
| CPU | AMD Ryzen 7 5800U with Radeon Graphics |
| GPU | Cezanne [Radeon Vega Series / Radeon Vega Mobile Series] (Advanced Micro Devices, Inc. [AMD/ATI]) |
| Memory | 31GB |
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
# Build Environment
┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
| Wails CLI | v3.0.0-beta.22 |
| Go Version | go1.27.0-X:nodwarf5 |
| -buildmode | exe |
| -compiler | gc |
| CGO_CFLAGS | |
| CGO_CPPFLAGS | |
| CGO_CXXFLAGS | |
| CGO_ENABLED | 1 |
| CGO_LDFLAGS | |
| DefaultGODEBUG | cryptocustomrand=1,tlssecpmlkem=0,tracebacklabels=0,urlstrictcolons=0,x509sslcertoverrideplatform=0 |
| GOAMD64 | v1 |
| GOARCH | amd64 |
| GOEXPERIMENT | nodwarf5 |
| GOOS | linux |
└──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
# Dependencies
┌──────────────────────────────────────────────────────────────────────────────────────────────┐
| *Android NDK | ~/Android/Sdk/ndk/30.0.16248370 |
| *Android SDK | ~/Android/Sdk |
| *Android platform-tools | Installed |
| *Java (Android) | openjdk version "26.0.2.1" 2026-08-18 |
| gcc | 16.2.1+r23+gd564253eb6c8-1 |
| gtk3 (legacy) | 1:3.24.52-1 |
| gtk4 | 1:4.22.4-1 |
| npm | 11.19.0 |
| pkg-config | 3.0.7-1 |
| webkit2gtk (legacy) | 2.52.6-1 |
| webkitgtk-6.0 | 2.52.6-1 |
| docker | *Docker version 29.7.2, build a7dcaa6fdb (cross-compilation ready) |
| |
└────────────────────────────────── * - Optional Dependency ───────────────────────────────────┘
# Signing
┌─────────────────────────────────────────────┐
| macOS Signing | Not configured |
| Windows Signing | Not configured |
| Linux Signing | GPG key: <hidden> |
└─────────────────────────────────────────────┘
# Checking for issues
SUCCESS No issues found
# Diagnosis
SUCCESS Your system is ready for Wails development!Additional context
References
- PR #5560 —
fix(updater/windows): fallback for cross-volume rename failures(Windows-only; Unix path untouched). v3/pkg/updater/helper_unix.go:replaceTarget— bareos.RemoveAll(target)+os.Rename(newPath, target).v3/pkg/updater/helper.go:runHelperSwap— retry loop, backup, restore, and relaunch flow surrounding the platform replace.
Source: wailsapp/wails