Skip to content

Fix Linux autostart reboot bug and release-publish race, add PR CI - #2

Merged
plainpacket merged 7 commits into
mainfrom
fix/linux-autostart-and-release-safety
Jul 26, 2026
Merged

Fix Linux autostart reboot bug and release-publish race, add PR CI#2
plainpacket merged 7 commits into
mainfrom
fix/linux-autostart-and-release-safety

Conversation

@plainpacket

@plainpacket plainpacket commented Jul 26, 2026

Copy link
Copy Markdown
Owner

Summary

Addresses a code review of the v0.4.0 release. All four original findings were independently verified before fixing; one review claim about how the AppImage-path fix would work was checked empirically and confirmed correct. A follow-up review of this same PR then found three more issues, also independently verified and fixed below.

Round 1

  • High -- Linux autostart silently breaks after reboot when run from the AppImage. resolveLinuxExecutablePath() now prefers process.env.APPIMAGE over process.execPath. Verified directly against a running built AppImage (temporary diagnostic build, not left in the code): process.execPath resolved to /tmp/.mount_PortPa.../portpatch -- a per-launch FUSE mount path that stops existing once that instance exits -- while process.env.APPIMAGE resolved to the real, stable release/PortPatch-0.4.0-linux-x86_64.AppImage path.
  • Medium -- $XDG_CONFIG_HOME was ignored. Now honored, with ""/unset falling back to ~/.config per spec.
  • High -- Windows and Linux release jobs each independently ran gh release create/upload. Restructured into windows-build/linux-build (artifact-only) + a single publish job with needs: [windows-build, linux-build].
  • Medium -- README contradicted the live v0.4.0 release, still saying the Linux AppImage wasn't published. Fixed.
  • Medium -- no CI ran on push/PR; tests only executed inside the release workflow. Added .github/workflows/ci.yml running pnpm test on both ubuntu-latest and windows-latest.

Round 2 (this PR, before merge)

  • % in the Exec value wasn't escaped. A path like .../100%free/... would have %f read as a Desktop Entry field code by a conforming launcher -- quoting doesn't prevent this, since field-code substitution is a separate pass. Now % is always escaped to %% regardless of whether the value also needs quoting. Added a test matching the exact reported example.
  • gh release upload --clobber could leave stale assets from a prior mistaken upload alongside new ones, since it only replaces files with matching names. The publish job now fails outright if a release already exists for the tag instead of silently reusing it -- replacing a published release is now a deliberate action (delete it first).
  • contents: write was set at the workflow level, so both build jobs carried it despite never touching repo contents themselves. Scoped to contents: read by default, contents: write only on publish.
  • Also added, from the same review's "additional recommendations": a check-version job that fails fast if the pushed tag doesn't match package.json's version; rejecting a relative XDG_CONFIG_HOME per the XDG Base Directory Specification (must be absolute); and running electron-runtime-smoke.js (real safeStorage/DPAPI round-trip + Tray creation, no BrowserWindow needed) on the Windows CI leg specifically -- not Linux, since safeStorage's Linux backends depend on a keyring/D-Bus session the ubuntu-latest runner doesn't have.
  • Not done: adding visual-smoke.js to CI. The review itself flagged it as having an intermittent timing issue and suggested stabilizing it first -- agreed, that's a separate, riskier change.

Not in scope here

  • No new release has been tagged. This fixes the bugs in source; a new version tag (e.g. v0.4.1) is a separate, deliberate step.
  • The intermittent Help-modal timing flake mentioned in round 1 review wasn't chased down (see above).

Test plan

  • pnpm test (59/59, up from 55 in the original release -- added XDG_CONFIG_HOME/relative-path, resolveLinuxExecutablePath, and %-escaping cases)
  • Built the AppImage with a temporary diagnostic and confirmed APPIMAGE/APPDIR/ARGV0/OWD env values directly against the running process, then reverted the diagnostic before committing
  • check-version bash logic tested locally for both matching and mismatching tag/version
  • Confirmed gh release view v0.4.0 succeeds against the real existing release, validating the new fail-if-exists check would correctly trigger
  • This PR's own ci.yml run is green on both ubuntu-latest and windows-latest, including the new electron-runtime-smoke.js step on Windows
  • Cut a new tagged release once merged, to exercise the restructured publish job (including check-version and the fail-if-exists check) for real

process.execPath inside a running AppImage resolves to a path under
a per-launch /tmp/.mount_* FUSE mount (confirmed directly against a
running instance), which stops existing once that instance exits, so
the autostart entry written with it pointed nowhere after the next
sign-in. The AppImage runtime sets APPIMAGE to the original file's
stable path before exec'ing the wrapped binary (also confirmed
directly); resolveLinuxExecutablePath() now prefers that when set.

Separately, autostartDirectory() always wrote to ~/.config/autostart
regardless of $XDG_CONFIG_HOME, which the XDG Base Directory
Specification says should take precedence when set.
Each platform job independently ran gh release view/create/upload,
so if one platform's job failed after the other had already
published, the release would end up with only one platform's asset;
concurrent create calls from both jobs were also a race. Both jobs
now only build and upload a workflow artifact; a new publish job
that needs both creates or updates the GitHub Release in one place,
with all four files, only once both builds have actually succeeded.
pnpm test previously only ran inside the release workflow, which
only triggers on a version tag -- meaning a broken test could only
be discovered by cutting a release. Adds a CI workflow that runs the
same test suite on both Windows and Linux for every push to main and
every pull request. electron-runtime-smoke.js and visual-smoke.js
still require a display and are not wired in here; they stay
manual/local for now rather than adding Xvfb/headless-GUI plumbing
in the same change.
README still said the Linux AppImage hadn't been released, but
v0.4.0 published both platform assets. Also documents the corrected
executable-path resolution and XDG_CONFIG_HOME handling in
architecture.md, including the two facts confirmed directly against
a running AppImage instance that motivated the fix.
A literal "%" in the Exec value (e.g. a path like .../100%free/...)
would have been read as the start of a field code (%f, %u, etc.) by
a conforming launcher, since NEEDS_QUOTING never covered "%" and
quoting doesn't affect field-code substitution anyway. Now always
escaped to "%%" regardless of whether the value also needs quoting.

Separately, the XDG Base Directory Specification requires XDG_*_HOME
values to be absolute and says a relative value should be treated as
invalid; autostartDirectory() now falls back to ~/.config in that
case instead of joining a relative path onto it.
Three changes to release.yml, all from external review:

- gh release upload --clobber only replaces files with matching
  names, so a release that already had unrelated or stale assets
  under its tag (e.g. from a past mistake, or a manually re-run tag)
  would keep them alongside the new upload. The publish job now
  fails outright if a release already exists for the tag rather than
  silently reusing it -- replacing a published release is now a
  deliberate action (delete it first), never an implicit side effect
  of re-running this workflow.
- contents: write was set at the workflow level, so both build jobs
  carried it despite never touching repo contents or the Releases
  API themselves (they only upload a build artifact). Scoped to
  contents: read by default, with contents: write added only on the
  publish job that actually needs it.
- Added a check-version job that both build jobs now depend on,
  comparing the pushed tag against package.json's version and
  failing fast if they don't match, instead of building and possibly
  publishing artifacts under a version string nobody intended.
electron-runtime-smoke.js exercises the real safeStorage encryption
round trip and Tray creation through actual Electron APIs rather
than the mocks the unit tests use, so it catches things those can't
(e.g. a genuinely broken DPAPI call). It needs no BrowserWindow/
display, so it isn't blocked by the same headless-GUI concerns as
visual-smoke.js. Only wired into the windows-latest matrix leg,
though, since safeStorage's Linux backends depend on a
gnome-keyring/D-Bus session that GitHub's ubuntu-latest runners
don't provide -- running it there would likely just be flaky rather
than meaningful.
@plainpacket
plainpacket merged commit a0f8007 into main Jul 26, 2026
2 checks passed
@plainpacket
plainpacket deleted the fix/linux-autostart-and-release-safety branch July 26, 2026 12:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant