Skip to content

fix(linux): Play from a desktop launcher no longer has systemd stop the game - #2540

Merged
NotRexed merged 1 commit into
NotRexed:mainfrom
klikso:linux/play-own-scope
Nov 12, 2026
Merged

NotRexed merged 1 commit into
NotRexed:mainfrom
klikso:linux/play-own-scope

Conversation

@klikso

@klikso klikso commented Nov 11, 2026

Copy link
Copy Markdown
Contributor

On Linux desktops that start each launcher as a systemd service (KDE/SteamOS, GNOME, COSMIC), pressing Play in the launcher window starts the game and then systemd kills it about 90 seconds later, which players see as a silent crash.

Fixes #2529

What changed

  • builder/linux/hookshot_builder.py: new own_scope(). When the Builder is running inside a .service cgroup and systemd-run is available, Play starts the game through systemd-run --user --scope --collect --quiet --, so it lives in a scope of its own and the launcher's service can stop without it. A trial systemd-run ... true runs first; if the user manager can't be reached (or there is no systemd, or the launcher is in a scope or a terminal), Play starts the game exactly as before.
  • tests/test_linux_play_scope.py (new, game-free, runs on Linux and Windows): when the scope is used and when it isn't (scope / cgroup v1 / no /proc / no systemd-run / failing trial / timeout), and that play() puts the prefix in front of both the diagnostics script and the direct-exe fallback.
  • tests/test_linux_play_diagnostics.py: stubs own_scope in the namespace it builds play() in.

The game not exiting on SIGTERM in a windowed run (also noted in the issue) is left for a separate PR.

How it was tested

  • python -I -B tests/run_all.py play builder on Windows: all pass (the Linux-only checks skip there).
  • Steam Deck (SteamOS, systemd 261): the new test passes. A stand-in launcher run as a transient user service (systemd-run --user --unit=... python3 launcher.py, the reporter's setup) that starts a long-lived child with start_new_session=True and exits:
    • before: the service stops and the child is gone;
    • after: the child keeps running in app.slice/run-p<pid>-i<n>.scope.
  • Not run in the game itself; no screen changes.

Assisted-by: Claude Code

🤖 Generated with Claude Code

…he game

A desktop that starts each launcher as a systemd user service stops that
service, and the game still in its control group, when the Builder window
closes after Play. Start the game in a scope of its own with
systemd-run --user --scope when the Builder runs in a .service and a trial
run works; otherwise start it as before.

Fixes NotRexed#2529

Assisted-by: Claude Code
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@toniosim

Copy link
Copy Markdown

Tested on COSMIC (Pop!_OS 24.04, systemd 255, NVIDIA 595.104.02, RTX 3070 Ti). Before this PR, Play from the launcher run as a user service (systemd-run --user, same as the desktop's app-hookshot@….service) made the unit go to stop-sigterm as soon as the Builder exited, and the game was SIGKILLed 90 s later (#2529).

With this PR's hookshot_builder.py (run with the v2.1.0 AppImage's Python/Tk), same setup, Play clicked: the unit went to inactive/dead cleanly, the game ran in its own run-r….scope, and it was still alive 151 s later, past the 90 s stop timeout. No SIGTERM/SIGKILL in the journal.

Caveat: this ran the PR's builder from source, not a packaged AppImage built from it.

DevL0rd added a commit to DevL0rd/Hookshot that referenced this pull request Nov 12, 2026
@NotRexed
NotRexed merged commit 50c81bb into NotRexed:main Nov 12, 2026
1 check passed
@mooki000

Copy link
Copy Markdown
Contributor

Thanks for the fix, and @toniosim for the analysis. I tested the merged change on Arch (Hyprland, systemd 262.1) with a transient user service shaped like the issue's launcher service and a stand-in for the game: on its base the child was killed 90.1 s after Play; with the fix it was still alive in its own scope after 96 s. So it fixes the reported case here.

Two cases are left, both shown by injection (not an estimate of how often they happen):

  • the probe with true succeeds but the real systemd-run then fails: Play starts nothing and does not fall back;
  • the real systemd-run is 250 ms slow: the window closes first, the launcher's service ends, and the game is never started.

#2643 is a small follow-up for both: Play waits (at most two seconds, usually no time at all) until the real runner has left the launcher's control group before the window closes, and starts the game the plain way exactly once if that does not happen. Ten ordinary launches behave as before on both. Not tested: a real desktop's Play click, GNOME, KDE or COSMIC, and a real log-out.

mooki000 added a commit to mooki000/Hookshot that referenced this pull request Nov 12, 2026
Wait for the real runner to leave the launcher's cgroup before Play closes the Builder. Gate the command on permission from the parent so a timed-out runner cannot start another game after plain fallback. Keep the existing scope choice and record a failed hand-off in startup diagnostics.

Cover failed, delayed, timed-out and immediately ready runners with an injected clock, plus the real diagnostics fallback notice.

Refs NotRexed#2529
Refs NotRexed#2540

Assisted-by: GPT-6 Astra
Co-Authored-By: GPT-6 Astra <noreply@openai.com>
elkarbeenus pushed a commit to elkarbeenus/LakuenRecompBuilder that referenced this pull request Nov 12, 2026
….2.0

Kept: this fork's low-end VRAM defaults, the handheld texture limits (with upstream's small-VRAM mips rule), the
Steam fixes, the native file choosers and the name. Taken from upstream: its Linux sleep timer (NotRexed#2637, this
fork's handheld rule), the RADV mesh default (NotRexed#2362), the systemd scope for Play (NotRexed#2540).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01C7fdTPXbwDRohew9qanvbp
dazemc added a commit to dazemc/Hookshot-rs that referenced this pull request Nov 12, 2026
Upstream NotRexed#2529, NotRexed#2540 and NotRexed#2643 describe the scope hand-off; players should know the window waits for it and that the fallback cannot bypass the desktop's service lifetime.
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.

Linux: Play from a desktop launcher gets the game killed ~90 s later (systemd stops the launcher's unit when the Builder exits)

4 participants