feat(host): a window right after a crash says what happened, with a Report button that copies a redacted report and opens the issue form #593

Merged
HumanBean merged 1 commit from HumanBean/Longshot:feat/crash-dialog into main 2026-11-14 23:04:00 +00:00
Owner

Type: feat · Risk: medium · Tested on: nothing built or run (game-free checks written, not run here) · Needs: Windows and Linux, any GPU

Why

When Longshot crashes, at the start or in play, the window just disappears: players don't know what happened, whether to update a driver, or where the log is, and a report needs several manual steps. This adds a clear window right after the crash, with a Report button that copies a redacted report and opens the issue form (the owner's request, option 1: a watcher process started with the game, not a window from inside the crash handler).

See #44 (local problem reports; its "the game would still send nothing and open no browser" is relaxed by the owner for this button only: the browser opens only when the player clicks Report, and the game sends nothing).

What changed

The game starts a small helper of itself early (longshot --crash-watch <pid> <run id> <game folder> <log>), which waits for the game to end and then decides from the crash records and the log whether to show "Longshot closed unexpectedly" (or "Longshot couldn't start"), when, and why where known. Report builds a plain-text report without anything private, puts it on the clipboard, saves it beside the crash records and opens the game-problem form; a crash record no helper showed is shown once at the next start.

  • host: host/hos_crash_dialog.h (new): the parts that decide, plain C for the checks: the helper's command line (hos_crash_args), which starts get a helper (hos_crash_watch_wanted), Windows argument quoting (hos_crash_quote_arg), the log's facts (hos_crash_log_line, hos_crash_stage, hos_crash_start_failed), the record (hos_crash_record_parse), was it a crash (hos_crash_verdict), the cause (hos_crash_cause) and the window's text, the menu's log redaction and value rule in C (hos_crash_redact_line, hos_crash_value_shown), the issue link (hos_crash_issue_url), and which record a start shows (hos_crash_show_at_start).
  • host: host/hos_crash_dialog.c (new): hos_crash_helper_main (main's first call: the helper never starts any of the game), hos_crash_watch_start (spawns the helper; Windows CreateProcessW with CREATE_NO_WINDOW, no inherited handles, out of the launcher's job where allowed, AllowSetForegroundWindow; Linux posix_spawn of /proc/self/exe with stdio on /dev/null, and the helper closes any other fd it inherited), the wait (Windows: process handle + exit code; Linux: pidfd, else the parent changing; a Linux restart's new run's marker ends the old helper), the window (SDL3/SDL2's SDL_ShowMessageBox loaded by the helper itself, MessageBoxW fallback on Windows), the clipboard (Windows' own; Linux wl-copy on Wayland, else xclip/xsel, which keep it after the helper ends, SDL's X11 clipboard only as the last resort; the box after Report says whether it was copied, never claims it when nothing took it, and names the saved report, which Open the report opens), the browser and folders (ShellExecuteW; SDL_OpenURL, else xdg-open), the report (the newest ten saved reports kept), the bug-reports/shown.txt ledger (replaced whole: written beside it, then renamed), no window while Windows signs out or shuts down (it would hold that up; a crash record is then shown at the next start), no helper and no next-start box under gamescope (Steam Deck Game Mode: GAMESCOPE_WAYLAND_DISPLAY, or XDG_CURRENT_DESKTOP=gamescope) until a box there is tested, and HOS_CRASH_TEST. With no log found (the game logged to a terminal or a pipe) the window says nothing about when, instead of "during the start".
  • host: host/hos_crash_record.c/.h: hos_crash_quit_asked (a "quit" note after the marker's session; recovery copies it into an unclean record as quit_asked: yes; it counts itself busy, and hos_crash_clean_end waits for it (bounded) before closing the marker, so the note never lands on a closed or reused descriptor), hos_crash_record_run/log/dir, hos_crash_record_recover (a start's recovery for the helper, no marker of its own, sharing a new setup() with hos_crash_record_start), hos_crash_record_marker. The crash writer's one marker write is now positional on Windows (WriteFile with an OVERLAPPED offset), so it and the quit note never depend on each other's file pointer; nothing else in the handler path changed.
  • host: host/rx_backend.c rx_close_asked: notes the quit first, before the renderer's close that can take seconds (window close, control socket quit, Restart now all go through it).
  • host: host/hos_main.c: both main()s hand a helper's command line over first; game_main starts the helper after the crash record and the run id banner (and hos_control_start), before the image, the guest, the renderer, sound and input; hos_crash_test_arm() after reserve_guest (the crash handlers); HOS_CRASH_TEST added to the names longshot_env.txt may not set.
  • docs: docs/development.md "The window after a crash" (the flow, the rule table, the causes, what Report holds, the switches, how to test it); docs/common-issues.md "Still stuck?" and the README's troubleshooting paragraph (what players see and the Report button).
  • nx2: make_game.py stale_copies: an update no longer ends the windowless crash helper of a game still open (its parent has the visible window).
  • tests: tests/test_crash_dialog.py (new; see Testing); tests/display_startup_failure_test.cpp stands in for hos_crash_helper_main (its fixture builds the Linux main(), which now calls it).

Was it a crash (hos_crash_verdict, agreeing with #498's rule "a quit never counts, however the process ends after it; a crash, or a kill with no quit asked for, still counts"):

How the run ended The window
a crash record of the run, a quit before it included yes
the log says the start could not go on before a save was loaded (game files, Vulkan, the window, cannot reserve the guest window) yes, "Longshot couldn't start" (a quit included: the black window closed)
the marker left, no crash record (killed, out of memory) yes, unless the player had asked to quit, or Windows' exit code is 0
a quit, then the process ended while closing no
a clean end, a return, Restart now (Windows: new process; Linux: same process, new run) no
--prepare-shaders, HOS_HEADLESS, HOS_PGO, HOS_CRASH_WATCH=0, gamescope no helper at all

Cause (first that holds): game files (cannot open, usage:, lift and image do not match, renderer version mismatch) → "Build the game again with the Longshot Launcher"; Vulkan (did not load ... nothing is drawn, load_global failed, vkCreateInstance failed, no Vulkan 1.3 GPU, create_device failed, the renderer did not start) → "Update your graphics driver: Longshot needs ... Vulkan 1.3"; the window (display: no SDL, SDL_Init, SDL_CreateWindow, ... failed (error) ; memory (cannot reserve, Windows is out of memory, commit failed, 0xC0000017, or an unclean end after boot: LOW MEMORY); the graphics driver (device lost, Vulkan fatal, a crash in nvoglv64/amdvlk/igvk/libvulkan_*/libnvidia-*...); an unclean end otherwise "closed from outside ... or the system stopped it"; else "An unexpected error". Stage: a map: line → "while you were playing"; preparation lines without "the shaders took" → "while it prepared the shaders"; else "during the start".

Report includes: what happened and when, uptime, crash/unclean records of the last day, version and channel (from the version), GPU and driver (log vk: lines) and the devices listed, CPU (brand string, threads), RAM, OS, what the game was built for (LONGSHOT_NATIVE_CPU or the CPU level), the log's first DLSS and FSR 4 lines, longshot_options.ini settings and switches set (a value only when a number, size or on/off; never for a name with TOKEN/KEY/ROOM/SECRET/PASS/SERVER/USER; controls' bindings left out), the run's crash record, and the last 200 log lines redacted exactly as the Insert menu's Bug report does. Never: saves, game files, the executable or caches, memory dumps, keys, the options file itself, the home folder's path or the user's name. "Crashes in a row" is not tracked by the host; the report gives the records of the last day instead.

Screenshots

No real screenshot is possible (nothing built here). A text mock-up of the window (SDL's message box) after a crash in play:

+-- Longshot closed unexpectedly ----------------------------------------+
| (x)  Longshot closed unexpectedly while you were playing.              |
|                                                                        |
|      What went wrong: The graphics driver failed. Update your graphics |
|      driver, and turn off overlays and tools hooked into the game.     |
|                                                                        |
|      Report copies a report to your clipboard (the error, your PC's    |
|      hardware and the end of the log, with your home folder and user   |
|      name taken out) and opens Longshot's issue form in your browser,  |
|      for you to paste it into. Longshot itself sends nothing.          |
|                                                                        |
|           [ Report ]   [ Open the log folder ]   [ Close ]             |
+------------------------------------------------------------------------+

After Report:

+-- Longshot: report ready ----------------------------------------------+
|  The report is on your clipboard. In the issue form that opened in     |
|  your browser, paste it into the longshot_log.txt box, say what you    |
|  were doing, and fill in the rest.                                     |
|                                                                        |
|  It is also saved in the game folder as                                |
|  bug-reports/pending/report-20261114T164012Z.txt.                      |
|                                                                        |
|  Keep this window open until you have pasted it. (Linux only)          |
|                                                                        |
|                 [ Open the report ]   [ Done ]                         |
+------------------------------------------------------------------------+

Measurements

Verdict: nothing measured yet: nothing was built or run on this PC (the owner's rule for it: its build/GPU queue is busy). What counts for a feat (it working in the game; no cost when off) needs a tester's run, below. The helper costs one small extra process per start (the same executable, no game started in it) that waits on the game's process; with HOS_CRASH_WATCH=0 none is started.

Hardware: none
Scene: none
Method: none yet; the game-free checks in tests/test_crash_dialog.py are written for CI and testers to run
How to reproduce: python -I -B tests/test_crash_dialog.py; in the game, the steps under Testing

Before: main @ 82886e89 After: this PR @ 75d3caf4
tests/test_crash_dialog.py (the file does not exist) not run here
not run on this PC

Flags and settings

Two switches added, and a file the game keeps (bug-reports/shown.txt). Existing switches now also read by the helper, unchanged: HOS_HEADLESS and HOS_PGO (no helper), HOS_SDL_VERSION=2 (the helper loads SDL2, as the game would).

Flag What it is Before this PR After this PR Effect Helps / doesn't help / untested Recommendation
HOS_CRASH_WATCH the crash window's helper (none) on; =0 off =0: no helper and no window after a crash untested on for everyone; =0 only for development runs
HOS_CRASH_TEST a crash on purpose (none) off unless set; start or <seconds>; not read from longshot_env.txt crashes the game just after its crash handlers are set up, or that many seconds after the start untested off: only for testing the window

Testing

Tested: nothing was built or run here, and no check was run (the owner's rule for this PC). The code was read against the crash record, relaunch, close and log paths it relies on.

Not tested: everything in the game, on both systems; the game-free checks. tests/test_crash_dialog.py checks: the helper's command line and its refusals; which starts get a helper; Windows quoting round-tripped through CommandLineToArgvW's rules; record names and times; the log's stage, start failures, memory, device lost, GPU and driver; the verdict and cause tables above; redaction equal to gpu/vk/vk_bug_report.cpp's redact line for line (compiled beside it); the value rule; the issue link (form, its field ids in 1-game-problem.yml, ≤ 2000 characters, the same form as the menu's kForm); the source wiring (helper first in both mains, started after the record and before the guest/renderer, HOS_CRASH_TEST after reserve_guest, the quit noted first in rx_close_asked, no console/inherited handles/game stdio, the helper closing inherited fds, no window at a Windows sign-out or shutdown, the ledger replaced whole, the saved reports pruned, the quit note and the clean end never overlapping on the marker, the display start-up fixture's stand-in for the helper, no process exits in the helper); the window saying nothing about when with no log; no helper under gamescope; Linux's clipboard tools in turn and the box after Report never saying copied when nothing took it (and opening the saved file); make_game.stale_copies sparing a game's waiting helper; and on Linux the real helper (-DHOS_CRASH_DIALOG_TEST: window, clipboard and browser stood in for) against real processes: crash, kill, quit then kill, crash after quit, clean end, Vulkan missing, execve restart, no helper for HOS_HEADLESS/HOS_CRASH_WATCH=0/--prepare-shaders/HOS_PGO, a crash no helper showed shown once at the next start, and Report's text (sections present; no saves, game files, keys, home path, user name, private values; the newest ten saved reports kept). Existing checks reading changed files (tests/test_crash_record.py's exit inventory and banner order, tests/test_startup_progress.py, tests/test_env_on.py, tests/test_env_file.py, tests/test_utf8_paths.py, tests/test_driver_cache_*.py, tests/test_hle_stats.py) were read and should still hold; tests/test_display_startup_failure.py did not (its fixture had no hos_crash_helper_main) and now has a stand-in. A code review found that and the fixes above (#593 (comment)); the Linux files were only syntax-checked (-fsyntax-only), nothing built or run.

Must be tested:

  • Linux and Windows: python -I -B tests/run_all.py, by hand (no checks may run on the author's PC), expecting test_crash_dialog.py, test_crash_record.py and test_display_startup_failure.py to pass.
  • Windows, any GPU, a release build: start with HOS_CRASH_TEST=start in the environment, expecting the window "Longshot closed unexpectedly during the start." right after the game closes, no console window flashing at start; click Open the log folder (Explorer on logs, the window again), then Report (the browser on the game-problem form with version, GPU, system and title filled in; the clipboard holds the report; bug-reports/pending/report-*.txt saved), paste into the form and check it holds no home path, user name, saves or keys (by hand).
  • Windows: HOS_CRASH_TEST=60, load a save within the minute, expecting "while you were playing"; end the game from Task Manager, expecting "ended without a crash report"; a normal quit and the Insert menu's Restart now, expecting no window (by hand).
  • Linux, X11 and Wayland desktops (and a Steam Deck in Desktop Mode), by hand: the same steps with kill -9 <pid> for the unclean end, expecting the window each time; Report, then paste in the browser, expecting the whole report; a quit, Restart now, and closing the window twice while the shaders are prepared, expecting no window. (Wayland done on KDE with a stand-in game: see the comment with the picture.)
  • Either system, by hand: kill the game and its helper together (the process tree), expecting the crash window at the next start once, and not at the start after.

Risks

What could break: an extra process per start: a launcher that counts child processes (Steam's reaper on Linux) sees it until the game ends, longer only while the window is open; a false window on an end that is no crash (an unclean end from a logout or a launcher's Stop where the helper survives); on Linux without wl-copy, xclip or xsel installed (and, on Wayland, no X11 fallback) nothing takes the text: the box then says so, names the saved report and opens it; on the Steam Deck's Game Mode the window is off (gamescope may not show a child process's box) until a Deck tester shows it works; on Windows the box may come up behind other windows once the game's is gone (a Windows tester wanted); nx2/make_game.py still ends a windowless helper whose game is gone (as any stray copy) during an update; on Linux, after a Restart now, the old helper is a zombie of the new run until the game exits (one per restart, harmless); SDL_ShowMessageBox is called before any SDL_Init, as SDL documents, and on Linux the second box runs on a thread while the main thread pumps SDL's events only when SDL's X11 clipboard is the fallback used. The crash writer's change on Windows is one positional WriteFile in place of a seek and a write.

Turning it off: HOS_CRASH_WATCH=0, or revert the commit.

Compatibility: nothing changes for saves, longshot_options.ini, the shader and pipeline caches, the vk ABI (MkwVkHost/MkwVkRenderer untouched, MKWVK_API_VERSION not bumped) or the builder's tools. Unclean records can carry a new quit_asked: yes line. The game folder gains bug-reports/shown.txt and, after a Report, bug-reports/pending/report-*.txt. The first start with this window lists the records already pending as shown, so no old crash pops up.

Issues

See #44

Checklist

Everyone

  • One topic.
  • The title is a Conventional Commit (type(scope): description) of the status line's type.
  • Why gives the reason plainly and links the issues that fit (the tracker searched).
  • Measurements has a verdict, or one line saying why there is nothing to measure.
  • Testing names what was tested, what wasn't, and what must still be tested, where and how.
  • Rebased on current main.
  • python -I -B tests/run_all.py passes (any failure that also fails on main is named). (not run: no checks may run on this PC; for CI or a tester)
  • No game files, dumps, build output or generated files.
  • third_party/ untouched (a new folder with a PROVENANCE.md instead).
  • The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled.

When it applies

  • AI assistance recorded with Assisted-by: / Co-Authored-By: trailers.
  • Switches documented (docs/development.md; the README for a menu row) and shipped at their recommended value.
  • Screenshots of the same spot, captioned like the Measurements columns, for a change that shows on screen. (the window on KDE/Wayland: #593 (comment))
  • Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths. (Linux: the real game builds and links with this PR, on the lab, clang 23.1.5. Windows: not built yet)
  • Docs updated.

🤖 Generated with Claude Code

**Type:** feat · **Risk:** medium · **Tested on:** nothing built or run (game-free checks written, not run here) · **Needs:** Windows and Linux, any GPU ## Why When Longshot crashes, at the start or in play, the window just disappears: players don't know what happened, whether to update a driver, or where the log is, and a report needs several manual steps. This adds a clear window right after the crash, with a **Report** button that copies a redacted report and opens the issue form (the owner's request, option 1: a watcher process started with the game, not a window from inside the crash handler). See https://git.magicbean.dev/MagicBeanDev/Longshot/issues/44 (local problem reports; its "the game would still send nothing and open no browser" is relaxed by the owner for this button only: the browser opens only when the player clicks Report, and the game sends nothing). ## What changed The game starts a small helper of itself early (`longshot --crash-watch <pid> <run id> <game folder> <log>`), which waits for the game to end and then decides from the crash records and the log whether to show "Longshot closed unexpectedly" (or "Longshot couldn't start"), when, and why where known. Report builds a plain-text report without anything private, puts it on the clipboard, saves it beside the crash records and opens the game-problem form; a crash record no helper showed is shown once at the next start. - host: `host/hos_crash_dialog.h` (new): the parts that decide, plain C for the checks: the helper's command line (`hos_crash_args`), which starts get a helper (`hos_crash_watch_wanted`), Windows argument quoting (`hos_crash_quote_arg`), the log's facts (`hos_crash_log_line`, `hos_crash_stage`, `hos_crash_start_failed`), the record (`hos_crash_record_parse`), **was it a crash** (`hos_crash_verdict`), the cause (`hos_crash_cause`) and the window's text, the menu's log redaction and value rule in C (`hos_crash_redact_line`, `hos_crash_value_shown`), the issue link (`hos_crash_issue_url`), and which record a start shows (`hos_crash_show_at_start`). - host: `host/hos_crash_dialog.c` (new): `hos_crash_helper_main` (main's first call: the helper never starts any of the game), `hos_crash_watch_start` (spawns the helper; Windows `CreateProcessW` with `CREATE_NO_WINDOW`, no inherited handles, out of the launcher's job where allowed, `AllowSetForegroundWindow`; Linux `posix_spawn` of `/proc/self/exe` with stdio on `/dev/null`, and the helper closes any other fd it inherited), the wait (Windows: process handle + exit code; Linux: pidfd, else the parent changing; a Linux restart's new run's marker ends the old helper), the window (SDL3/SDL2's `SDL_ShowMessageBox` loaded by the helper itself, `MessageBoxW` fallback on Windows), the clipboard (Windows' own; Linux `wl-copy` on Wayland, else `xclip`/`xsel`, which keep it after the helper ends, SDL's X11 clipboard only as the last resort; the box after Report says whether it was copied, never claims it when nothing took it, and names the saved report, which Open the report opens), the browser and folders (`ShellExecuteW`; `SDL_OpenURL`, else `xdg-open`), the report (the newest ten saved reports kept), the `bug-reports/shown.txt` ledger (replaced whole: written beside it, then renamed), no window while Windows signs out or shuts down (it would hold that up; a crash record is then shown at the next start), no helper and no next-start box under gamescope (Steam Deck Game Mode: `GAMESCOPE_WAYLAND_DISPLAY`, or `XDG_CURRENT_DESKTOP=gamescope`) until a box there is tested, and `HOS_CRASH_TEST`. With no log found (the game logged to a terminal or a pipe) the window says nothing about when, instead of "during the start". - host: `host/hos_crash_record.c/.h`: `hos_crash_quit_asked` (a "quit" note after the marker's session; recovery copies it into an unclean record as `quit_asked: yes`; it counts itself busy, and `hos_crash_clean_end` waits for it (bounded) before closing the marker, so the note never lands on a closed or reused descriptor), `hos_crash_record_run/log/dir`, `hos_crash_record_recover` (a start's recovery for the helper, no marker of its own, sharing a new `setup()` with `hos_crash_record_start`), `hos_crash_record_marker`. The crash writer's one marker write is now positional on Windows (`WriteFile` with an `OVERLAPPED` offset), so it and the quit note never depend on each other's file pointer; nothing else in the handler path changed. - host: `host/rx_backend.c` `rx_close_asked`: notes the quit first, before the renderer's close that can take seconds (window close, control socket quit, Restart now all go through it). - host: `host/hos_main.c`: both `main()`s hand a helper's command line over first; `game_main` starts the helper after the crash record and the run id banner (and `hos_control_start`), before the image, the guest, the renderer, sound and input; `hos_crash_test_arm()` after `reserve_guest` (the crash handlers); `HOS_CRASH_TEST` added to the names `longshot_env.txt` may not set. - docs: `docs/development.md` "The window after a crash" (the flow, the rule table, the causes, what Report holds, the switches, how to test it); `docs/common-issues.md` "Still stuck?" and the README's troubleshooting paragraph (what players see and the Report button). - nx2: `make_game.py` `stale_copies`: an update no longer ends the windowless crash helper of a game still open (its parent has the visible window). - tests: `tests/test_crash_dialog.py` (new; see Testing); `tests/display_startup_failure_test.cpp` stands in for `hos_crash_helper_main` (its fixture builds the Linux `main()`, which now calls it). **Was it a crash** (`hos_crash_verdict`, agreeing with https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/498's rule "a quit never counts, however the process ends after it; a crash, or a kill with no quit asked for, still counts"): | How the run ended | The window | |---|---| | a crash record of the run, a quit before it included | yes | | the log says the start could not go on before a save was loaded (game files, Vulkan, the window, `cannot reserve the guest window`) | yes, "Longshot couldn't start" (a quit included: the black window closed) | | the marker left, no crash record (killed, out of memory) | yes, unless the player had asked to quit, or Windows' exit code is 0 | | a quit, then the process ended while closing | no | | a clean end, a return, Restart now (Windows: new process; Linux: same process, new run) | no | | `--prepare-shaders`, `HOS_HEADLESS`, `HOS_PGO`, `HOS_CRASH_WATCH=0`, gamescope | no helper at all | **Cause** (first that holds): game files (`cannot open`, `usage:`, `lift and image do not match`, renderer version mismatch) → "Build the game again with the Longshot Launcher"; Vulkan (`did not load ... nothing is drawn`, `load_global failed`, `vkCreateInstance failed`, `no Vulkan 1.3 GPU`, `create_device failed`, `the renderer did not start`) → "Update your graphics driver: Longshot needs ... Vulkan 1.3"; the window (`display: no SDL`, `SDL_Init`, `SDL_CreateWindow`, `... failed (error`) ; memory (`cannot reserve`, `Windows is out of memory`, commit failed, `0xC0000017`, or an unclean end after `boot: LOW MEMORY`); the graphics driver (`device lost`, `Vulkan fatal`, a crash in `nvoglv64`/`amdvlk`/`igvk`/`libvulkan_*`/`libnvidia-*`...); an unclean end otherwise "closed from outside ... or the system stopped it"; else "An unexpected error". Stage: a `map:` line → "while you were playing"; preparation lines without "the shaders took" → "while it prepared the shaders"; else "during the start". **Report includes:** what happened and when, uptime, crash/unclean records of the last day, version and channel (from the version), GPU and driver (log `vk:` lines) and the devices listed, CPU (brand string, threads), RAM, OS, what the game was built for (`LONGSHOT_NATIVE_CPU` or the CPU level), the log's first DLSS and FSR 4 lines, `longshot_options.ini` settings and switches set (a value only when a number, size or on/off; never for a name with TOKEN/KEY/ROOM/SECRET/PASS/SERVER/USER; controls' bindings left out), the run's crash record, and the last 200 log lines redacted exactly as the Insert menu's Bug report does. **Never:** saves, game files, the executable or caches, memory dumps, keys, the options file itself, the home folder's path or the user's name. "Crashes in a row" is not tracked by the host; the report gives the records of the last day instead. ## Screenshots No real screenshot is possible (nothing built here). A text mock-up of the window (SDL's message box) after a crash in play: ``` +-- Longshot closed unexpectedly ----------------------------------------+ | (x) Longshot closed unexpectedly while you were playing. | | | | What went wrong: The graphics driver failed. Update your graphics | | driver, and turn off overlays and tools hooked into the game. | | | | Report copies a report to your clipboard (the error, your PC's | | hardware and the end of the log, with your home folder and user | | name taken out) and opens Longshot's issue form in your browser, | | for you to paste it into. Longshot itself sends nothing. | | | | [ Report ] [ Open the log folder ] [ Close ] | +------------------------------------------------------------------------+ ``` After Report: ``` +-- Longshot: report ready ----------------------------------------------+ | The report is on your clipboard. In the issue form that opened in | | your browser, paste it into the longshot_log.txt box, say what you | | were doing, and fill in the rest. | | | | It is also saved in the game folder as | | bug-reports/pending/report-20261114T164012Z.txt. | | | | Keep this window open until you have pasted it. (Linux only) | | | | [ Open the report ] [ Done ] | +------------------------------------------------------------------------+ ``` ## Measurements **Verdict:** nothing measured yet: nothing was built or run on this PC (the owner's rule for it: its build/GPU queue is busy). What counts for a `feat` (it working in the game; no cost when off) needs a tester's run, below. The helper costs one small extra process per start (the same executable, no game started in it) that waits on the game's process; with `HOS_CRASH_WATCH=0` none is started. Hardware: none Scene: none Method: none yet; the game-free checks in `tests/test_crash_dialog.py` are written for CI and testers to run How to reproduce: `python -I -B tests/test_crash_dialog.py`; in the game, the steps under Testing | | Before: main @ 82886e89 | After: this PR @ 75d3caf4 | |---|---|---| | `tests/test_crash_dialog.py` | (the file does not exist) | not run here | ``` not run on this PC ``` ### Flags and settings Two switches added, and a file the game keeps (`bug-reports/shown.txt`). Existing switches now also read by the helper, unchanged: `HOS_HEADLESS` and `HOS_PGO` (no helper), `HOS_SDL_VERSION=2` (the helper loads SDL2, as the game would). | Flag | What it is | Before this PR | After this PR | Effect | Helps / doesn't help / untested | Recommendation | |---|---|---|---|---|---|---| | `HOS_CRASH_WATCH` | the crash window's helper | (none) | on; `=0` off | `=0`: no helper and no window after a crash | untested | on for everyone; `=0` only for development runs | | `HOS_CRASH_TEST` | a crash on purpose | (none) | off unless set; `start` or `<seconds>`; not read from `longshot_env.txt` | crashes the game just after its crash handlers are set up, or that many seconds after the start | untested | off: only for testing the window | ## Testing **Tested:** nothing was built or run here, and no check was run (the owner's rule for this PC). The code was read against the crash record, relaunch, close and log paths it relies on. **Not tested:** everything in the game, on both systems; the game-free checks. `tests/test_crash_dialog.py` checks: the helper's command line and its refusals; which starts get a helper; Windows quoting round-tripped through CommandLineToArgvW's rules; record names and times; the log's stage, start failures, memory, device lost, GPU and driver; the verdict and cause tables above; redaction equal to `gpu/vk/vk_bug_report.cpp`'s `redact` line for line (compiled beside it); the value rule; the issue link (form, its field ids in `1-game-problem.yml`, ≤ 2000 characters, the same form as the menu's `kForm`); the source wiring (helper first in both mains, started after the record and before the guest/renderer, `HOS_CRASH_TEST` after `reserve_guest`, the quit noted first in `rx_close_asked`, no console/inherited handles/game stdio, the helper closing inherited fds, no window at a Windows sign-out or shutdown, the ledger replaced whole, the saved reports pruned, the quit note and the clean end never overlapping on the marker, the display start-up fixture's stand-in for the helper, no process exits in the helper); the window saying nothing about when with no log; no helper under gamescope; Linux's clipboard tools in turn and the box after Report never saying copied when nothing took it (and opening the saved file); `make_game.stale_copies` sparing a game's waiting helper; and on Linux the real helper (`-DHOS_CRASH_DIALOG_TEST`: window, clipboard and browser stood in for) against real processes: crash, kill, quit then kill, crash after quit, clean end, Vulkan missing, execve restart, no helper for `HOS_HEADLESS`/`HOS_CRASH_WATCH=0`/`--prepare-shaders`/`HOS_PGO`, a crash no helper showed shown once at the next start, and Report's text (sections present; no saves, game files, keys, home path, user name, private values; the newest ten saved reports kept). Existing checks reading changed files (`tests/test_crash_record.py`'s exit inventory and banner order, `tests/test_startup_progress.py`, `tests/test_env_on.py`, `tests/test_env_file.py`, `tests/test_utf8_paths.py`, `tests/test_driver_cache_*.py`, `tests/test_hle_stats.py`) were read and should still hold; `tests/test_display_startup_failure.py` did not (its fixture had no `hos_crash_helper_main`) and now has a stand-in. A code review found that and the fixes above (https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/593#issuecomment-20914); the Linux files were only syntax-checked (`-fsyntax-only`), nothing built or run. **Must be tested:** - Linux and Windows: `python -I -B tests/run_all.py`, by hand (no checks may run on the author's PC), expecting `test_crash_dialog.py`, `test_crash_record.py` and `test_display_startup_failure.py` to pass. - Windows, any GPU, a release build: start with `HOS_CRASH_TEST=start` in the environment, expecting the window "Longshot closed unexpectedly during the start." right after the game closes, no console window flashing at start; click Open the log folder (Explorer on `logs`, the window again), then Report (the browser on the game-problem form with version, GPU, system and title filled in; the clipboard holds the report; `bug-reports/pending/report-*.txt` saved), paste into the form and check it holds no home path, user name, saves or keys (by hand). - Windows: `HOS_CRASH_TEST=60`, load a save within the minute, expecting "while you were playing"; end the game from Task Manager, expecting "ended without a crash report"; a normal quit and the Insert menu's Restart now, expecting no window (by hand). - Linux, X11 and Wayland desktops (and a Steam Deck in Desktop Mode), by hand: the same steps with `kill -9 <pid>` for the unclean end, expecting the window each time; Report, then paste in the browser, expecting the whole report; a quit, Restart now, and closing the window twice while the shaders are prepared, expecting no window. (Wayland done on KDE with a stand-in game: see the comment with the picture.) - Either system, by hand: kill the game and its helper together (the process tree), expecting the crash window at the next start once, and not at the start after. ## Risks **What could break:** an extra process per start: a launcher that counts child processes (Steam's reaper on Linux) sees it until the game ends, longer only while the window is open; a false window on an end that is no crash (an unclean end from a logout or a launcher's Stop where the helper survives); on Linux without `wl-copy`, `xclip` or `xsel` installed (and, on Wayland, no X11 fallback) nothing takes the text: the box then says so, names the saved report and opens it; on the Steam Deck's Game Mode the window is off (gamescope may not show a child process's box) until a Deck tester shows it works; on Windows the box may come up behind other windows once the game's is gone (a Windows tester wanted); `nx2/make_game.py` still ends a windowless helper whose game is gone (as any stray copy) during an update; on Linux, after a Restart now, the old helper is a zombie of the new run until the game exits (one per restart, harmless); `SDL_ShowMessageBox` is called before any `SDL_Init`, as SDL documents, and on Linux the second box runs on a thread while the main thread pumps SDL's events only when SDL's X11 clipboard is the fallback used. The crash writer's change on Windows is one positional `WriteFile` in place of a seek and a write. **Turning it off:** `HOS_CRASH_WATCH=0`, or revert the commit. **Compatibility:** nothing changes for saves, `longshot_options.ini`, the shader and pipeline caches, the vk ABI (`MkwVkHost`/`MkwVkRenderer` untouched, `MKWVK_API_VERSION` not bumped) or the builder's tools. Unclean records can carry a new `quit_asked: yes` line. The game folder gains `bug-reports/shown.txt` and, after a Report, `bug-reports/pending/report-*.txt`. The first start with this window lists the records already pending as shown, so no old crash pops up. ## Issues See https://git.magicbean.dev/MagicBeanDev/Longshot/issues/44 ## Checklist ### Everyone - [x] One topic. - [x] The title is a Conventional Commit (`type(scope): description`) of the status line's type. - [x] Why gives the reason plainly and links the issues that fit (the tracker searched). - [x] Measurements has a verdict, or one line saying why there is nothing to measure. - [x] Testing names what was tested, what wasn't, and what must still be tested, where and how. - [x] Rebased on current `main`. - [ ] `python -I -B tests/run_all.py` passes (any failure that also fails on `main` is named). (not run: no checks may run on this PC; for CI or a tester) - [x] No game files, dumps, build output or generated files. - [x] `third_party/` untouched (a new folder with a `PROVENANCE.md` instead). - [x] The license is OK: GPL-3.0-or-later, nothing incompatible and nothing decompiled. ### When it applies - [x] AI assistance recorded with `Assisted-by:` / `Co-Authored-By:` trailers. - [x] Switches documented (docs/development.md; the README for a menu row) and shipped at their recommended value. - [x] Screenshots of the same spot, captioned like the Measurements columns, for a change that shows on screen. (the window on KDE/Wayland: https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/593#issuecomment-20998) - [ ] Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths. (Linux: the real game builds and links with this PR, on the lab, clang 23.1.5. Windows: not built yet) - [x] Docs updated. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(host): a window right after a crash says what happened, with a Report button that copies a redacted report and opens the issue form
Some checks are pending
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 4s
beanstalk/approval awaiting maintainer approval
CI / Workflow lint (actionlint) (pull_request) Successful in 10s
Third-party parts / As their notes say (pull_request) Successful in 28s
PR description / Description follows the template (pull_request_target) Successful in 15s
3822fb340d
The game starts a small helper of itself early (longshot --crash-watch), which waits for
the game to end and, after a crash, an unclean end or a start that could not go on, shows
"Longshot closed unexpectedly" with when, why where known, and Report / Open the log
folder / Close. Report copies a redacted plain-text report, writes it beside the crash
records and opens the game-problem issue form; nothing is sent by the game. A quit never
counts (noted first in rx_close_asked), a restart says nothing, and a crash record no
helper showed is shown once at the next start. HOS_CRASH_WATCH=0 turns it off;
HOS_CRASH_TEST=start|<seconds> crashes on purpose for testing.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Author
Owner

Flags and settings: named the existing switches the helper also reads (HOS_HEADLESS, HOS_PGO, HOS_SDL_VERSION). Stays WIP: nothing built or run yet; needs CI and testers on Windows and Linux.

Flags and settings: named the existing switches the helper also reads (HOS_HEADLESS, HOS_PGO, HOS_SDL_VERSION). Stays WIP: nothing built or run yet; needs CI and testers on Windows and Linux.
HumanBean left a comment

Code review by reading (nothing built or run; the Linux helper's files compiled with -fsyntax-only only for the fixes).

Checked and fine: the crash path is unchanged apart from the Windows marker write, and that write is right: a positional WriteFile with an OVERLAPPED offset on a synchronous handle writes at that offset whatever the file pointer is, record_complete (inside Session) and the quit note (at sizeof(Session)) never overlap, neither touches the locked byte 0, and recovery reads the note with its own handle. Nothing new runs in a signal or exception handler. Quoting (trailing backslashes, quotes, spaces, UTF-8 to wide), no inherited handles, CREATE_NO_WINDOW, the breakaway retry, /proc/self/exe with stdio on /dev/null, the pidfd and parent fallbacks, the run-id-keyed record and log lookups (two games in one folder, rotated logs, an old record), the quit-during-precompile path (mkwvk_close_asked lets the start finish, so no "could not start" line is logged), --prepare-shaders/HOS_HEADLESS/HOS_PGO, the redaction (the menu's own), the value rule, the link's encoding and length, MKWVK_API_VERSION (vk_api.h untouched), third_party/, no CP_ACP, and the cut-outs in test_crash_record.py, test_env_on.py, test_env_file.py, test_utf8_paths.py, test_startup_progress.py, test_driver_cache_*.py and test_hle_stats.py.

Found (inline):

  1. bug: tests/test_display_startup_failure.py cuts the Linux main() into a C++ fixture that has no hos_crash_helper_main, so it no longer links.
  2. risk: the quit note can write to a marker that hos_crash_clean_end is closing at the same moment (control-socket quit on one thread, window close on another). On Linux a reused fd would get quit\n written into another file.
  3. risk: on Linux the helper keeps every fd the game inherited without close-on-exec (a launcher's pipe, for example), for as long as the window is open.
  4. risk: on Windows the game ended by a logoff or shutdown wakes the helper, which then shows a box that holds up the shutdown.
  5. risk: when the log isn't found (stderr to a terminal or a pipe, or a --crash-show whose log has rotated past .1), the window says "during the start" even for a crash in play.
  6. nit: bug-reports/pending/report-*.txt files are never pruned (prune_pending only takes crash-/unclean-).
  7. nit: shown.txt is rewritten in place. A start that reads it mid-write sees an empty ledger and shows a crash again.
  8. nit: "Keep this window open until you have pasted it" is also said on Windows, where the clipboard keeps the text after the helper exits.
  9. nit, not fixed: on Linux, after a Restart now (execve), the old helper stays a zombie child of the new run until the game exits. It is harmless (one per restart), and reaping it would mean reaping children the game doesn't own.

Needs a build or a tester: whether the SDL box comes to the front on Windows once the game's window is gone; whether gamescope (Steam Deck Game Mode) shows a child process's box at all (if not, the helper waits on an invisible box and Steam shows the game as still running); whether the Wayland clipboard takes the text (SDL3 can report success when it doesn't); nx2/make_game.py ends windowless longshot.exe copies in the folder during a build, which would end a waiting helper with a "copy left running" message; whether the launcher counts the helper as the game while the window is open.

Fixes for 1 to 8 follow on the branch, with checks.

Code review by reading (nothing built or run; the Linux helper's files compiled with `-fsyntax-only` only for the fixes). **Checked and fine:** the crash path is unchanged apart from the Windows marker write, and that write is right: a positional `WriteFile` with an `OVERLAPPED` offset on a synchronous handle writes at that offset whatever the file pointer is, `record_complete` (inside `Session`) and the quit note (at `sizeof(Session)`) never overlap, neither touches the locked byte 0, and recovery reads the note with its own handle. Nothing new runs in a signal or exception handler. Quoting (trailing backslashes, quotes, spaces, UTF-8 to wide), no inherited handles, `CREATE_NO_WINDOW`, the breakaway retry, `/proc/self/exe` with stdio on `/dev/null`, the pidfd and parent fallbacks, the run-id-keyed record and log lookups (two games in one folder, rotated logs, an old record), the quit-during-precompile path (`mkwvk_close_asked` lets the start finish, so no "could not start" line is logged), `--prepare-shaders`/`HOS_HEADLESS`/`HOS_PGO`, the redaction (the menu's own), the value rule, the link's encoding and length, `MKWVK_API_VERSION` (vk_api.h untouched), `third_party/`, no `CP_ACP`, and the cut-outs in `test_crash_record.py`, `test_env_on.py`, `test_env_file.py`, `test_utf8_paths.py`, `test_startup_progress.py`, `test_driver_cache_*.py` and `test_hle_stats.py`. **Found** (inline): 1. **bug**: `tests/test_display_startup_failure.py` cuts the Linux `main()` into a C++ fixture that has no `hos_crash_helper_main`, so it no longer links. 2. **risk**: the quit note can write to a marker that `hos_crash_clean_end` is closing at the same moment (control-socket quit on one thread, window close on another). On Linux a reused fd would get `quit\n` written into another file. 3. **risk**: on Linux the helper keeps every fd the game inherited without close-on-exec (a launcher's pipe, for example), for as long as the window is open. 4. **risk**: on Windows the game ended by a logoff or shutdown wakes the helper, which then shows a box that holds up the shutdown. 5. **risk**: when the log isn't found (stderr to a terminal or a pipe, or a `--crash-show` whose log has rotated past `.1`), the window says "during the start" even for a crash in play. 6. **nit**: `bug-reports/pending/report-*.txt` files are never pruned (`prune_pending` only takes `crash-`/`unclean-`). 7. **nit**: `shown.txt` is rewritten in place. A start that reads it mid-write sees an empty ledger and shows a crash again. 8. **nit**: "Keep this window open until you have pasted it" is also said on Windows, where the clipboard keeps the text after the helper exits. 9. **nit, not fixed**: on Linux, after a Restart now (execve), the old helper stays a zombie child of the new run until the game exits. It is harmless (one per restart), and reaping it would mean reaping children the game doesn't own. **Needs a build or a tester:** whether the SDL box comes to the front on Windows once the game's window is gone; whether gamescope (Steam Deck Game Mode) shows a child process's box at all (if not, the helper waits on an invisible box and Steam shows the game as still running); whether the Wayland clipboard takes the text (SDL3 can report success when it doesn't); `nx2/make_game.py` ends windowless `longshot.exe` copies in the folder during a build, which would end a waiting helper with a "copy left running" message; whether the launcher counts the helper as the game while the window is open. Fixes for 1 to 8 follow on the branch, with checks.
@ -0,0 +306,4 @@
Keep k = {&out, all || !old ? NULL : old}; /* (no ledger yet: what was pending is not news) */
each_name(pending, keep_listed, &k);
if (name && !hos_crash_listed(out.s, name)) put(&out, name), put(&out, "\n");
const int ok = write_text(path, out.s ? out.s : "", out.n);
Author
Owner

nit: the ledger is truncated and rewritten in place. A start reading it in between (two runs, or a helper and a start) gets "", an existing but empty ledger, and shows a crash of the last day again. Fix: write a temporary file and rename it over shown.txt.

**nit**: the ledger is truncated and rewritten in place. A start reading it in between (two runs, or a helper and a start) gets `""`, an existing but empty ledger, and shows a crash of the last day again. Fix: write a temporary file and rename it over `shown.txt`.
@ -0,0 +817,4 @@
char pending[PATH_MAX_UTF8], path[PATH_MAX_UTF8], leaf[64], stamp[17];
utc_now(stamp);
snprintf(leaf, sizeof leaf, "report-%s.txt", stamp);
const int saved = pending_of(s->dir, pending, sizeof pending) && join(path, sizeof path, pending, leaf) && write_text(path, text, n);
Author
Owner

nit: report-*.txt files collect in bug-reports/pending for good: prune_pending (hos_crash_record.c) only prunes crash-/unclean- names. Fix: keep the newest 10 after each save.

**nit**: `report-*.txt` files collect in `bug-reports/pending` for good: `prune_pending` (hos_crash_record.c) only prunes `crash-`/`unclean-` names. Fix: keep the newest 10 after each save.
@ -0,0 +832,4 @@
"doing, and fill in the rest.");
else put(&m, "Open " HOS_CRASH_ISSUES ", choose Game problem, and paste it into the longshot_log.txt box.");
if (saved) putf(&m, "\n\nIt is also saved in the game folder as bug-reports/pending/%s.", leaf);
if (copied) put(&m, "\n\nKeep this window open until you have pasted it.");
Author
Owner

nit: on Windows the text stays on the clipboard after the helper exits (SetClipboardData hands the memory to the system), so this line only applies to Linux, where the helper serves the X11 selection itself.

**nit**: on Windows the text stays on the clipboard after the helper exits (`SetClipboardData` hands the memory to the system), so this line only applies to Linux, where the helper serves the X11 selection itself.
@ -0,0 +903,4 @@
sleep_ms(250);
}
if (hos_crash_record_marker(a->dir, a->pid, a->run_id) == 3) { /* (restarted: execve, a new run's marker) */
if (fd >= 0) close(fd);
Author
Owner

nit, not fixed: on Linux the helper is a child of the game. After an execve restart this helper returns and stays a zombie of the new run until the game exits: one zombie per restart, harmless. Reaping it from the new run would mean waitpid(-1, ...) on children the game doesn't own, which is worse than the zombie.

**nit, not fixed**: on Linux the helper is a child of the game. After an execve restart this helper returns and stays a zombie of the new run until the game exits: one zombie per restart, harmless. Reaping it from the new run would mean `waitpid(-1, ...)` on children the game doesn't own, which is worse than the zombie.
@ -0,0 +949,4 @@
if (find_run_log(log, a->log, run_id)) read_log(log, &s.who);
s.verdict = hos_crash_verdict(&r, &log->facts, have_exit, code, 0);
if (a->mode == 2 && r.kind != 1) s.verdict = HOS_CRASH_QUIET;
if (s.verdict != HOS_CRASH_QUIET) {
Author
Owner

risk (Windows): at a logoff or shutdown the session ends the game (often with a non-zero exit code and the marker left), the helper wakes up, and SDL_ShowMessageBox/MessageBoxW puts up a box that holds the shutdown ("Longshot closed unexpectedly is preventing you from signing out"). Fix: when GetSystemMetrics(SM_SHUTTINGDOWN) is set, show nothing and don't list the record in shown.txt, so a real crash record is still shown at the next start (unclean ends never are).

**risk (Windows)**: at a logoff or shutdown the session ends the game (often with a non-zero exit code and the marker left), the helper wakes up, and `SDL_ShowMessageBox`/`MessageBoxW` puts up a box that holds the shutdown ("Longshot closed unexpectedly is preventing you from signing out"). Fix: when `GetSystemMetrics(SM_SHUTTINGDOWN)` is set, show nothing and don't list the record in `shown.txt`, so a real crash record is still shown at the next start (unclean ends never are).
@ -0,0 +950,4 @@
s.verdict = hos_crash_verdict(&r, &log->facts, have_exit, code, 0);
if (a->mode == 2 && r.kind != 1) s.verdict = HOS_CRASH_QUIET;
if (s.verdict != HOS_CRASH_QUIET) {
s.stage = hos_crash_stage(&log->facts);
Author
Owner

risk: with no log found (stderr to a terminal or a pipe, so -; or a --crash-show whose run's log has rotated past .1), the stage is HOS_STAGE_START and the window says "closed unexpectedly during the start" even for a crash an hour into play. Fix: an unknown stage when the log wasn't found, and the sentence then says nothing about when.

**risk**: with no log found (stderr to a terminal or a pipe, so `-`; or a `--crash-show` whose run's log has rotated past `.1`), the stage is `HOS_STAGE_START` and the window says "closed unexpectedly during the start" even for a crash an hour into play. Fix: an unknown stage when the log wasn't found, and the sentence then says nothing about when.
@ -0,0 +1027,4 @@
posix_spawn_file_actions_addopen(&fa, 1, "/dev/null", O_WRONLY, 0);
posix_spawn_file_actions_addopen(&fa, 2, "/dev/null", O_WRONLY, 0);
pid_t pid;
const int ok = posix_spawn(&pid, "/proc/self/exe", &fa, NULL, argv, environ) == 0;
Author
Owner

risk: posix_spawn passes on every fd that isn't close-on-exec. The game's own fds are (the marker, maps, the control socket), but whatever a launcher or a shell leaked into the game isn't, and the helper would hold it while it waits and while its window is open. A pipe held like that keeps a reader that waits for EOF waiting. Fix: the helper closes fds 3 and up first thing (close_range, else /proc/self/fd). posix_spawn_file_actions_addclosefrom_np would raise the glibc minimum to 2.34.

**risk**: `posix_spawn` passes on every fd that isn't close-on-exec. The game's own fds are (the marker, maps, the control socket), but whatever a launcher or a shell leaked into the game isn't, and the helper would hold it while it waits and while its window is open. A pipe held like that keeps a reader that waits for EOF waiting. Fix: the helper closes fds 3 and up first thing (`close_range`, else `/proc/self/fd`). `posix_spawn_file_actions_addclosefrom_np` would raise the glibc minimum to 2.34.
@ -823,0 +843,4 @@
DWORD n = 0;
WriteFile(marker, note, sizeof note, &n, &at);
#else
if (pwrite(marker, note, sizeof note, (off_t)sizeof(Session)) != (ssize_t)sizeof note) return;
Author
Owner

risk: there is a gap between this function's ready check and its write. hos_crash_clean_end on another thread (the control socket's game_quit, a window close and an atexit can overlap) can close marker in that gap. On Linux, if another thread then opens a file, the write lands on a reused fd, and quit\n goes into that file at offset ~4.3 KB. On Windows it writes to a closed or reused handle. The quit_noted exchange doesn't cover this, because the second caller returns at once and goes straight on to clean_end. Fix: count the writers (quit_busy, sequentially consistent), and have clean_end, after clearing ready, wait briefly (bounded) for the count to drop before it closes the marker.

**risk**: there is a gap between this function's `ready` check and its write. `hos_crash_clean_end` on another thread (the control socket's `game_quit`, a window close and an `atexit` can overlap) can close `marker` in that gap. On Linux, if another thread then opens a file, the write lands on a reused fd, and `quit\n` goes into that file at offset ~4.3 KB. On Windows it writes to a closed or reused handle. The `quit_noted` exchange doesn't cover this, because the second caller returns at once and goes straight on to `clean_end`. Fix: count the writers (`quit_busy`, sequentially consistent), and have `clean_end`, after clearing `ready`, wait briefly (bounded) for the count to drop before it closes the marker.
host/hos_main.c Outdated
@ -2086,1 +2097,4 @@
int main(int argc, char **argv) {
{ /* (longshot) the crash window's helper of this run (host/hos_crash_dialog.c): first, so it starts nothing of the game */
int hos_crash_helper_main(int argc, char **argv);
const int helper = hos_crash_helper_main(argc, argv);
Author
Owner

bug: tests/test_display_startup_failure.py cuts this main() out (function(host, "int main(int argc, char **argv)"), the last one), renames it app_main and builds it with tests/display_startup_failure_test.cpp, which defines no hos_crash_helper_main. The fixture now fails to link (an undefined hos_crash_helper_main(int, char**)), so the whole check fails. Fix: add a stub that returns -1 (the game itself) to the fixture.

**bug**: `tests/test_display_startup_failure.py` cuts this `main()` out (`function(host, "int main(int argc, char **argv)")`, the last one), renames it `app_main` and builds it with `tests/display_startup_failure_test.cpp`, which defines no `hos_crash_helper_main`. The fixture now fails to link (an undefined `hos_crash_helper_main(int, char**)`), so the whole check fails. Fix: add a stub that returns -1 (the game itself) to the fixture.
HumanBean force-pushed feat/crash-dialog from 3822fb340d
Some checks are pending
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 4s
beanstalk/approval awaiting maintainer approval
CI / Workflow lint (actionlint) (pull_request) Successful in 10s
Third-party parts / As their notes say (pull_request) Successful in 28s
PR description / Description follows the template (pull_request_target) Successful in 15s
to 8ad59bc809
All checks were successful
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 6s
CI / Workflow lint (actionlint) (pull_request) Successful in 10s
Third-party parts / As their notes say (pull_request) Successful in 11s
PR description / Description follows the template (pull_request_target) Successful in 4s
2026-11-14 22:16:30 +00:00
Compare
Author
Owner

Code review (#593 (comment)) found, and 8ad59bc8 fixes: tests/test_display_startup_failure.py no longer linked (its fixture builds the Linux main(), now calling hos_crash_helper_main: a stand-in added); the quit note could write to a marker hos_crash_clean_end was closing on another thread (now counted busy, and the clean end waits, bounded); the Linux helper kept fds the game inherited without close-on-exec (now closed first); on Windows a sign-out or shutdown would get a window that holds it up (now none, and the record isn't listed so a crash is shown at the next start); with no log found the window said 'during the start' (now nothing about when); saved reports were never pruned (newest ten kept); shown.txt was rewritten in place (now replaced whole); 'Keep this window open' was said on Windows too (Linux only now). Checks added in tests/test_crash_dialog.py for each; docs updated. Not fixed: the Linux zombie after an execve restart (harmless). Still WIP: nothing built or run (the Linux files only -fsyntax-only); needs a build, run_all, and Windows, Linux and Steam Deck testers.

Code review (https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/593#issuecomment-20914) found, and 8ad59bc8 fixes: tests/test_display_startup_failure.py no longer linked (its fixture builds the Linux main(), now calling hos_crash_helper_main: a stand-in added); the quit note could write to a marker hos_crash_clean_end was closing on another thread (now counted busy, and the clean end waits, bounded); the Linux helper kept fds the game inherited without close-on-exec (now closed first); on Windows a sign-out or shutdown would get a window that holds it up (now none, and the record isn't listed so a crash is shown at the next start); with no log found the window said 'during the start' (now nothing about when); saved reports were never pruned (newest ten kept); shown.txt was rewritten in place (now replaced whole); 'Keep this window open' was said on Windows too (Linux only now). Checks added in tests/test_crash_dialog.py for each; docs updated. Not fixed: the Linux zombie after an execve restart (harmless). Still WIP: nothing built or run (the Linux files only -fsyntax-only); needs a build, run_all, and Windows, Linux and Steam Deck testers.
HumanBean force-pushed feat/crash-dialog from 8ad59bc809
All checks were successful
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 6s
CI / Workflow lint (actionlint) (pull_request) Successful in 10s
Third-party parts / As their notes say (pull_request) Successful in 11s
PR description / Description follows the template (pull_request_target) Successful in 4s
to 75d3caf4ea
Some checks failed
CI / Python source checks (ubuntu-24.04, part 1/2) (pull_request) Has been skipped
CI / Python source checks (ubuntu-24.04, part 2/2) (pull_request) Has been skipped
CI / SPIR-V fixtures (global narrow load/store, direct surface atomics, F2I immediate) (pull_request) Has been skipped
Third-party parts / The same as upstream (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 5s
CI / Workflow lint (actionlint) (pull_request) Successful in 11s
Third-party parts / As their notes say (pull_request) Successful in 19s
PR description / Description follows the template (pull_request_target) Failing after 5s
2026-11-14 22:27:18 +00:00
Compare
Author
Owner

75d3caf4, the owner's follow-ups: (1) Linux clipboard: Report now copies through wl-copy on Wayland, else xclip/xsel (they keep the text after the helper ends; reaped, no zombie), SDL's X11 clipboard only as the last resort and never on Wayland (it needs a focused window and claimed success anyway; its video init also put the next box on a thread, which is likely why the 'report ready' box didn't show and the helper exited). The box after Report never says copied when nothing took it, names the saved report's path and opens it (Open the report, or by itself when no box can be shown). (2) No helper and no next-start box under gamescope (GAMESCOPE_WAYLAND_DISPLAY, or XDG_CURRENT_DESKTOP=gamescope; not SteamDeck=1, which Desktop Mode has too) until a Deck tester shows a child's box appears there. (3) nx2/make_game.py: an update no longer ends the windowless crash helper of a game still open (stale_copies: parent has the visible window). Checks for each in tests/test_crash_dialog.py; docs updated. Not run here (helper compiled standalone only). Still WIP.

75d3caf4, the owner's follow-ups: (1) Linux clipboard: Report now copies through wl-copy on Wayland, else xclip/xsel (they keep the text after the helper ends; reaped, no zombie), SDL's X11 clipboard only as the last resort and never on Wayland (it needs a focused window and claimed success anyway; its video init also put the next box on a thread, which is likely why the 'report ready' box didn't show and the helper exited). The box after Report never says copied when nothing took it, names the saved report's path and opens it (Open the report, or by itself when no box can be shown). (2) No helper and no next-start box under gamescope (GAMESCOPE_WAYLAND_DISPLAY, or XDG_CURRENT_DESKTOP=gamescope; not SteamDeck=1, which Desktop Mode has too) until a Deck tester shows a child's box appears there. (3) nx2/make_game.py: an update no longer ends the windowless crash helper of a game still open (stale_copies: parent has the visible window). Checks for each in tests/test_crash_dialog.py; docs updated. Not run here (helper compiled standalone only). Still WIP.
Author
Owner

Linux build of the real game with this PR (main 82886e89 + 75d3caf4's changes, dev_packs base on the lab, i7-9700K, clang 23.1.5): compiles and links (BUILD OK; the changed host files, hos_main.c and rx_backend.c's hooks included). On a KDE/Wayland desktop (DISPLAY unset), the helper compiled standalone against a stand-in game killed after writing a crash record: the window shows at once with the right stage, Report writes the report, wl-copy holds the full 77-line report on the clipboard after the helper exits, and the pre-filled issue form opens. Windows: not compiled or run yet.

Linux build of the real game with this PR (main 82886e89 + 75d3caf4's changes, dev_packs base on the lab, i7-9700K, clang 23.1.5): compiles and links (BUILD OK; the changed host files, hos_main.c and rx_backend.c's hooks included). On a KDE/Wayland desktop (DISPLAY unset), the helper compiled standalone against a stand-in game killed after writing a crash record: the window shows at once with the right stage, Report writes the report, wl-copy holds the full 77-line report on the clipboard after the helper exits, and the pre-filled issue form opens. Windows: not compiled or run yet.
Author
Owner

The crash window on a KDE/Wayland desktop (Linux, the helper compiled from this PR against a stand-in game killed after writing a crash record):

crash-window.png

The crash window on a KDE/Wayland desktop (Linux, the helper compiled from this PR against a stand-in game killed after writing a crash record): ![crash-window.png](https://git.magicbean.dev/attachments/903e7558-6f9b-4af6-90b0-f9d31f0aeb3a)
Author
Owner

Description: the Must be tested items say who runs them and what to expect; the screenshot box ticked with the Wayland picture; the builds box says Linux builds (the lab) and Windows isn't built yet.

Description: the Must be tested items say who runs them and what to expect; the screenshot box ticked with the Wayland picture; the builds box says Linux builds (the lab) and Windows isn't built yet.
HumanBean changed title from WIP: feat(host): a window right after a crash says what happened, with a Report button that copies a redacted report and opens the issue form to feat(host): a window right after a crash says what happened, with a Report button that copies a redacted report and opens the issue form 2026-11-14 23:03:51 +00:00
HumanBean deleted branch feat/crash-dialog 2026-11-14 23:04:00 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
MagicBeanDev/Longshot!593
No description provided.