#6134·wails

Linux updater helper fails across filesystems (EXDEV), strands user with no working binary

Author: john-okeefeCreated Sep 17, 2026Updated Sep 17, 2026
LabelsBugv3

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 — bare os.RemoveAll(target) + os.Rename(newPath, target).
  • v3/pkg/updater/helper.go:runHelperSwap — retry loop, backup, restore, and relaunch flow surrounding the platform replace.