feat: a crash window that shows the crash report and sends it only on Send Help #141

Open
gab9281 wants to merge 10 commits from gab9281/Longshot:feat/crash-report-window into main
Member

Why

When Longshot crashes, the player gets nothing and the team learns nothing unless someone digs out
longshot_log.txt and posts it. With this PR and LongshotLauncher#46, a window opens after a crash. It shows exactly what a report holds, and
sends it to #81 only when the player presses Send Help. I am sorry sends nothing.

Since the review, the window is the Longshot Launcher's (--crash <folder>, LongshotLauncher#46): main removed both builder windows (#171, #181). This PR keeps the report (nx2/crash_report.py), the game's side (host/hos_crash_window.c, which starts builder=, the launcher) and the report: lines.

What changed

  • docs: docs/crash-report-window.md: what the player sees, what a report holds and never holds, the release flag,
    how the parts fit, Always send, the steps left, and what testing on real builds found. The beans moved to the launcher.
  • nx2: nx2/crash_report.py, run by the launcher's crash window and imported by the tests:
    • pending() finds the crashed run's log through crash_pending.txt, including after a rotation.
    • build() makes the report:
      • Fields: version, release flag, OS, CPU and RAM, GPU and driver, renderer, map.
      • Settings: every setting, defaults included.
      • Crash: the CRASH: lines without the register and memory rows, and the 150 log lines before them.
      • Folders are shown as ~ (Wine and Proton's Z:\home\<name> too), and the user name is redacted wherever it appears as a word (e.g. -home-<user>- in a
        path written with dashes), in any case, except a name that is also a word of the report's lines (a Steam Deck's
        user is deck), in its own case only, so the report still says "Steam Deck". Every field stays within the relay's limits. The OS and CPU come from the machine when the log has no line.
      • The crash's place is one the relay takes: libc.so.6+0x… without the library's path, and unknown+0x10 for a
        place in no module.
    • send() posts it to the relay. A 2xx is sent whatever its body; anything that goes wrong before an answer keeps
      the report for next time.
    • The relay's and #81's addresses are in nx2/addresses.ini, not in the code (one place to edit when a service moves).
    • Device names (the microphones', the audio output's) never reach the log lines.
    • card(), settings_text(), log_text(): the window's rows and Show texts.
    • For the launcher: --window (one line of JSON: the rows, the Show texts, the marker's time; nothing sent) and
      --send --json --time <t> (the send's answer; the marker dropped unless the report is kept). The send is a process
      of its own: the window can close during a slow send and it finishes.
    • clear(folder, when) drops only the marker of the crash a window opened for.
    • always_send() and set_always_send() are the launcher's tick.
    • comment_link() is the Sent page's link.
  • host: host/hos_crash_window.c:
    • hos_crash_window_init() builds the command once at startup, from longshot_version.txt's builder= (the
      launcher that built the folder: LONGSHOT_BUILDER_EXE) and builder_args=.
    • hos_crash_window_open() runs in report_crash (host/mkw_glue.c) after the log is flushed. It writes
      crash_pending.txt, then starts <launcher> --crash <folder>.
      • Windows: CreateProcessW, detached.
      • Linux: posix_spawn from the signal handler, its attributes made at startup: every signal at its default and
        none blocked, its own session, output to /dev/null, every fd above 2 closed.
    • The AppImage and LongshotBuilder.exe answer --crash with nothing (a folder they built names them as builder=).
    • It runs once per process. LONGSHOT_NO_CRASH_WINDOW follows the switches' rule (env_on: 0, false, off, no are off).
  • host/vk: report: gpu|cpu|os|renderer lines in the startup log, which the report reads:
    • cpu_text() and os_text() in gpu/vk/vk_bug_report.cpp;
    • the log lines in gpu/vk/vk_main.cpp and host/rx_backend.c.
  • tests:
    • tests/test_crash_report.py: 31 tests on the report, --window and --send --json among them.
    • tests/test_crash_window.py: compiles hos_crash_window.c with a stand-in launcher; the window starts with the
      crash's signal unblocked and none of the game's files.

Flags and settings

Flag What it does Default now Recommendation Helps on Doesn't help on Untested on
crash_reports (longshot_options.ini) 0: a crash opens no window and writes no marker on (absent counts as on) On by default: nothing is sent without a click all — the menu row doesn't exist yet: it's set by hand
LONGSHOT_NO_CRASH_WINDOW=1 turns the crash window off for a run unset Set for tests, benchmarks and CI automated runs — —
longshot_crash.ini always_send=1 the crash window's "Always send crash reports" tick: the window still shows the report, and sends it without waiting for Send Help off Off by default: it's the player's choice players who always want to send — Windows outside Wine
--window, --send --json [--time <t>] (nx2/crash_report.py) what the launcher's crash window reads and how it sends — For LongshotLauncher#46; not for players — — —

Measurements

The new code runs at startup (it builds one command) and at a crash, so the check is that startup and play are
unchanged, and how long the window takes after a crash. Measured with RX_VK_FRAME_LOG, the game started as the
launcher starts it, 60 s each, no input (the title and the attract demo), frames from 20 s on; interleaved main, PR,
main, PR in the same folder layout (work/game-linux-main and work/game-linux, built the same way). RTX 3060 Laptop
(NVIDIA 620.70.06), Ryzen 7 6800H, 15 GB, CachyOS (Linux 7.3.3), KDE Wayland, the default settings.

main: 6b7fa356 (2026-11-13). The PR is now on main @ 514591a3; the numbers still hold: since bb847be7 the game's
code changed only in how the crash starts the window (posix_spawn, its attributes made once at startup), which
play never runs. The crash row was measured again at 8cdc358d (below).
this PR: feat/crash-report-window @ bb847be7 (the game's code is the same at 6c9b7e7a: after it came the builders'
and the report's review fixes, and a rebase on main @ 36a12f6f: the website, the compile-shaders choice, the builders'
self-update removal; none of it in the game's start or play measured here)

main @ 6b7fa356 this PR @ bb847be7
First present after the start (s), median (runs) 3.06 (3.36, 2.76) 2.74 (2.81, 2.66)
Frame time (ms), median (runs) 33.84 (33.73, 33.95) 33.83 (33.75, 33.90)
Average fps (runs) 29.45 (29.6, 29.3) 29.45 (29.6, 29.3)
1% low fps (runs) 27.9 (28.8, 27.0) 27.75 (28.6, 26.9)
A crash (kill -SEGV at 25 s) to <launcher> --crash running (s), median (runs) none: nothing opens (no marker either) 0.11 (0.09, 0.13), the marker written; at 8cdc358d with the launcher: 0.11

Startup and play are the same within run-to-run noise (the title runs at its 30 fps either way). The crash row is when
the window's process is running; its window is drawn a moment after.

Testing

Tested:

  • Game-free: RX_CPM=<the CPM cache> python -I -B tests/run_all.py on 11684c08 (main @ 514591a3): 519 passed, 1 failed,
    15 skipped (Windows only); the failure, tests/test_menu_i18n.py (a pt_BR text of a menu line main changed), fails on
    main 514591a3 too. On 25ee9472 (main @ fbd42024): 516 passed, 0 failed. tests/test_crash_window.py's two new checks fail without the child's fix and pass with
    it. hos_crash_window.c builds with llvm-mingw -Werror.
  • In the game, Linux, with the launcher (LongshotLauncher#46 @ 86f79d2): OoT built from 8cdc358d (and again from 25ee9472, the posix_spawn start: the same results, SigIgn only the launcher's own SIGPIPE) into a folder whose
    builder= is the launcher (set by hand: a build from a checkout doesn't write the stamp), on an RTX 3060 Laptop (NVIDIA 620.70.06), a Ryzen 7 6800H with 15 GB, CachyOS (Linux
    7.3.3), KDE Wayland. Started as the launcher starts it, kill -SEGV at 25 s:
    • the launcher's --crash ran 0.11 s later, with SigBlk: 0000000000000000 (the crash's signal unblocked) and none
      of the game's files open;
    • its window showed the real report (screenshot): the GPU, CPU and OS of this PC, "Title and intro", the crash at
      libc.so.6+0xb5a22, 150 log lines;
    • the send, the window's own command against a stand-in relay on 127.0.0.1 (not #81, already tested before):
      sent, and the marker dropped.
  • Before the move (builders' windows, at 608dd588 and 752ff889 under Wine 11.21): Send Help posted to #81 from a real
    crash on Linux and on Windows, and the relay's parser (Bean-Ops) accepted both reports.

Not tested:

  • Windows itself with the launcher's window: LongshotLauncher#46 has no Windows run yet.
  • A real crash in play: these crashes were a signal sent from outside (Linux) and a thread started at a bad
    address (Windows, before the move), both before the first map. So the map is "Title and intro".
  • The release flag on a release: CI doesn't write source= yet (a step left), so every report is "not a release"
    for now.

Needs other testers:

  • Windows 10/11, not Wine: a crash in a build made by the launcher with LongshotLauncher#46 opens the window, Send
    Help posts to #81, expecting the window within a few seconds and the comment's link working (T-YFML; its notes
    will be updated for the launcher).
  • AMD and Intel GPUs: the report: gpu line names the card and driver, expecting both in the window's Graphics card
    row (T-FTCD).

Screenshots

main @ 514591a3 has no crash window: a crash ends the game with nothing on screen. This PR @ 8cdc358d with
LongshotLauncher#46 @ 86f79d2, a real crash (kill -SEGV on the running game), nothing edited:

the launcher's crash window after a real crash

The builders' windows of the earlier rounds (Linux Tk, Windows Qt under Wine) are gone with the builders.

Risk and rollback

  • The crash path itself: hos_crash_window_open() runs inside the crash handler. On Linux that's a signal
    handler, so it uses only open/write/vfork/execve and allocates nothing. If it fails, the crash ends as it
    does on main, with the marker left for the launcher's next start.
  • Turning it off: crash_reports=0 or LONGSHOT_NO_CRASH_WINDOW=1. Reverting the PR is enough otherwise.
  • Privacy: the window shows everything before it sends, and sends only on a click. Paths are redacted to ~,
    and settings like bindings and the microphone's name are never sent.

Reviewer notes

  • Start with host/hos_crash_window.c (what runs in the crash handler: posix_spawn with attributes made at startup), then nx2/crash_report.py build() (what
    leaves the PC). The window is LongshotLauncher#46; both should merge together (the launcher's window says "This
    build's tools can't read crash reports yet" with builders from before this PR).
  • Folders built from a checkout get no longshot_version.txt, so no builder= line and no window. Releases have
    one.
  • The game writes longshot_log.txt only when it isn't started from a terminal. Started from a terminal, it logs
    there, and the window finds no crash to report.
  • Left for follow-ups on purpose, listed in the doc's Steps:
    • CI's source= line (the release flag);
    • longshot_defaults.ini;
    • the Crash reports row in the in-game menu and in recomplauncher.json;
    • the AGENTS.md rule.
  • The relay's side (the release field, [not a release]) is already deployed in Bean-Ops.

Issues

See #81

Checklist

For everyone

  • One topic.
  • The title is a Conventional Commit (type(scope): description).
  • Rebased on current main.
  • python -I -B tests/run_all.py run and passing (any failure that also fails on main is named above).
  • Tested in the game, or what wasn't is named under Testing.
  • Testing filled in: what was tested, what wasn't, and what needs other testers (with test requests).
  • Measured: main and this PR at named commits, on the same PC, scene and settings, at least 2 interleaved runs each, the median reported.
  • 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. (Both beans are Magic Bean's own art.)

If applicable

  • AI assistance recorded with Assisted-by: / Co-Authored-By: trailers.
  • Flags noted, and the switch reference updated (docs/development.md, the README).
  • Screenshots for a change that shows on screen.
  • A game-free test added for the fix, failing on main and passing here.
  • A check under tests/ that reads changed source updated in this PR, not weakened.
  • Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths. (After the third review: hos_crash_window.c again with llvm-mingw -Werror; the rx_backend.c change is one log line.)
  • Docs updated.

🤖 Generated with Claude Code

https://claude.ai/code/session_015KgSKoUqUvce5UXG89NBgi

## Why When Longshot crashes, the player gets nothing and the team learns nothing unless someone digs out `longshot_log.txt` and posts it. With this PR and [LongshotLauncher#46](https://git.magicbean.dev/MagicBeanDev/LongshotLauncher/pulls/46), a window opens after a crash. It shows exactly what a report holds, and sends it to #81 only when the player presses **Send Help**. **I am sorry** sends nothing. Since the review, the window is the Longshot Launcher's (`--crash <folder>`, LongshotLauncher#46): main removed both builder windows (#171, #181). This PR keeps the report (`nx2/crash_report.py`), the game's side (`host/hos_crash_window.c`, which starts `builder=`, the launcher) and the `report:` lines. ## What changed - docs: `docs/crash-report-window.md`: what the player sees, what a report holds and never holds, the release flag, how the parts fit, Always send, the steps left, and what testing on real builds found. The beans moved to the launcher. - nx2: `nx2/crash_report.py`, run by the launcher's crash window and imported by the tests: - `pending()` finds the crashed run's log through `crash_pending.txt`, including after a rotation. - `build()` makes the report: - Fields: version, release flag, OS, CPU and RAM, GPU and driver, renderer, map. - Settings: every setting, defaults included. - Crash: the `CRASH:` lines without the register and memory rows, and the 150 log lines before them. - Folders are shown as `~` (Wine and Proton's `Z:\home\<name>` too), and the user name is redacted wherever it appears as a word (e.g. `-home-<user>-` in a path written with dashes), in any case, except a name that is also a word of the report's lines (a Steam Deck's user is `deck`), in its own case only, so the report still says "Steam Deck". Every field stays within the relay's limits. The OS and CPU come from the machine when the log has no line. - The crash's place is one the relay takes: `libc.so.6+0x…` without the library's path, and `unknown+0x10` for a place in no module. - `send()` posts it to the relay. A 2xx is sent whatever its body; anything that goes wrong before an answer keeps the report for next time. - The relay's and #81's addresses are in `nx2/addresses.ini`, not in the code (one place to edit when a service moves). - Device names (the microphones', the audio output's) never reach the log lines. - `card()`, `settings_text()`, `log_text()`: the window's rows and Show texts. - For the launcher: `--window` (one line of JSON: the rows, the Show texts, the marker's time; nothing sent) and `--send --json --time <t>` (the send's answer; the marker dropped unless the report is kept). The send is a process of its own: the window can close during a slow send and it finishes. - `clear(folder, when)` drops only the marker of the crash a window opened for. - `always_send()` and `set_always_send()` are the launcher's tick. - `comment_link()` is the Sent page's link. - host: `host/hos_crash_window.c`: - `hos_crash_window_init()` builds the command once at startup, from `longshot_version.txt`'s `builder=` (the launcher that built the folder: `LONGSHOT_BUILDER_EXE`) and `builder_args=`. - `hos_crash_window_open()` runs in `report_crash` (`host/mkw_glue.c`) after the log is flushed. It writes `crash_pending.txt`, then starts `<launcher> --crash <folder>`. - Windows: `CreateProcessW`, detached. - Linux: `posix_spawn` from the signal handler, its attributes made at startup: every signal at its default and none blocked, its own session, output to `/dev/null`, every fd above 2 closed. - The AppImage and `LongshotBuilder.exe` answer `--crash` with nothing (a folder they built names them as `builder=`). - It runs once per process. `LONGSHOT_NO_CRASH_WINDOW` follows the switches' rule (`env_on`: 0, false, off, no are off). - host/vk: `report: gpu|cpu|os|renderer` lines in the startup log, which the report reads: - `cpu_text()` and `os_text()` in `gpu/vk/vk_bug_report.cpp`; - the log lines in `gpu/vk/vk_main.cpp` and `host/rx_backend.c`. - tests: - `tests/test_crash_report.py`: 31 tests on the report, `--window` and `--send --json` among them. - `tests/test_crash_window.py`: compiles `hos_crash_window.c` with a stand-in launcher; the window starts with the crash's signal unblocked and none of the game's files. ## Flags and settings | Flag | What it does | Default now | Recommendation | Helps on | Doesn't help on | Untested on | |---|---|---|---|---|---|---| | `crash_reports` (`longshot_options.ini`) | `0`: a crash opens no window and writes no marker | on (absent counts as on) | On by default: nothing is sent without a click | all | — | the menu row doesn't exist yet: it's set by hand | | `LONGSHOT_NO_CRASH_WINDOW=1` | turns the crash window off for a run | unset | Set for tests, benchmarks and CI | automated runs | — | — | | `longshot_crash.ini` `always_send=1` | the crash window's "Always send crash reports" tick: the window still shows the report, and sends it without waiting for Send Help | off | Off by default: it's the player's choice | players who always want to send | — | Windows outside Wine | | `--window`, `--send --json [--time <t>]` (`nx2/crash_report.py`) | what the launcher's crash window reads and how it sends | — | For LongshotLauncher#46; not for players | — | — | — | ## Measurements The new code runs at startup (it builds one command) and at a crash, so the check is that startup and play are unchanged, and how long the window takes after a crash. Measured with `RX_VK_FRAME_LOG`, the game started as the launcher starts it, 60 s each, no input (the title and the attract demo), frames from 20 s on; interleaved main, PR, main, PR in the same folder layout (`work/game-linux-main` and `work/game-linux`, built the same way). RTX 3060 Laptop (NVIDIA 620.70.06), Ryzen 7 6800H, 15 GB, CachyOS (Linux 7.3.3), KDE Wayland, the default settings. main: 6b7fa356 (2026-11-13). The PR is now on main @ 514591a3; the numbers still hold: since bb847be7 the game's code changed only in how the crash starts the window (`posix_spawn`, its attributes made once at startup), which play never runs. The crash row was measured again at 8cdc358d (below). this PR: feat/crash-report-window @ bb847be7 (the game's code is the same at 6c9b7e7a: after it came the builders' and the report's review fixes, and a rebase on main @ 36a12f6f: the website, the compile-shaders choice, the builders' self-update removal; none of it in the game's start or play measured here) | | main @ 6b7fa356 | this PR @ bb847be7 | |---|---|---| | First present after the start (s), median (runs) | 3.06 (3.36, 2.76) | 2.74 (2.81, 2.66) | | Frame time (ms), median (runs) | 33.84 (33.73, 33.95) | 33.83 (33.75, 33.90) | | Average fps (runs) | 29.45 (29.6, 29.3) | 29.45 (29.6, 29.3) | | 1% low fps (runs) | 27.9 (28.8, 27.0) | 27.75 (28.6, 26.9) | | A crash (`kill -SEGV` at 25 s) to `<launcher> --crash` running (s), median (runs) | none: nothing opens (no marker either) | 0.11 (0.09, 0.13), the marker written; at 8cdc358d with the launcher: 0.11 | Startup and play are the same within run-to-run noise (the title runs at its 30 fps either way). The crash row is when the window's process is running; its window is drawn a moment after. ## Testing **Tested:** - Game-free: `RX_CPM=<the CPM cache> python -I -B tests/run_all.py` on 11684c08 (main @ 514591a3): 519 passed, 1 failed, 15 skipped (Windows only); the failure, `tests/test_menu_i18n.py` (a pt_BR text of a menu line main changed), fails on main 514591a3 too. On 25ee9472 (main @ fbd42024): 516 passed, 0 failed. `tests/test_crash_window.py`'s two new checks fail without the child's fix and pass with it. `hos_crash_window.c` builds with llvm-mingw `-Werror`. - In the game, Linux, with the launcher (LongshotLauncher#46 @ 86f79d2): OoT built from 8cdc358d (and again from 25ee9472, the posix_spawn start: the same results, `SigIgn` only the launcher's own SIGPIPE) into a folder whose `builder=` is the launcher (set by hand: a build from a checkout doesn't write the stamp), on an RTX 3060 Laptop (NVIDIA 620.70.06), a Ryzen 7 6800H with 15 GB, CachyOS (Linux 7.3.3), KDE Wayland. Started as the launcher starts it, `kill -SEGV` at 25 s: - the launcher's `--crash` ran 0.11 s later, with `SigBlk: 0000000000000000` (the crash's signal unblocked) and none of the game's files open; - its window showed the real report (screenshot): the GPU, CPU and OS of this PC, "Title and intro", the crash at `libc.so.6+0xb5a22`, 150 log lines; - the send, the window's own command against a stand-in relay on 127.0.0.1 (not #81, already tested before): sent, and the marker dropped. - Before the move (builders' windows, at 608dd588 and 752ff889 under Wine 11.21): Send Help posted to #81 from a real crash on Linux and on Windows, and the relay's parser (Bean-Ops) accepted both reports. **Not tested:** - Windows itself with the launcher's window: LongshotLauncher#46 has no Windows run yet. - A real crash in play: these crashes were a signal sent from outside (Linux) and a thread started at a bad address (Windows, before the move), both before the first map. So the map is "Title and intro". - The release flag on a release: CI doesn't write `source=` yet (a step left), so every report is "not a release" for now. **Needs other testers:** - Windows 10/11, not Wine: a crash in a build made by the launcher with LongshotLauncher#46 opens the window, Send Help posts to #81, expecting the window within a few seconds and the comment's link working (T-YFML; its notes will be updated for the launcher). - AMD and Intel GPUs: the `report: gpu` line names the card and driver, expecting both in the window's Graphics card row (T-FTCD). ## Screenshots main @ 514591a3 has no crash window: a crash ends the game with nothing on screen. This PR @ 8cdc358d with LongshotLauncher#46 @ 86f79d2, a real crash (`kill -SEGV` on the running game), nothing edited: ![the launcher's crash window after a real crash](https://git.magicbean.dev/attachments/c614206f-aeec-4448-b9eb-9ecdd20a7156) The builders' windows of the earlier rounds (Linux Tk, Windows Qt under Wine) are gone with the builders. ## Risk and rollback - **The crash path itself:** `hos_crash_window_open()` runs inside the crash handler. On Linux that's a signal handler, so it uses only `open`/`write`/`vfork`/`execve` and allocates nothing. If it fails, the crash ends as it does on `main`, with the marker left for the launcher's next start. - **Turning it off:** `crash_reports=0` or `LONGSHOT_NO_CRASH_WINDOW=1`. Reverting the PR is enough otherwise. - **Privacy:** the window shows everything before it sends, and sends only on a click. Paths are redacted to `~`, and settings like bindings and the microphone's name are never sent. ## Reviewer notes - Start with `host/hos_crash_window.c` (what runs in the crash handler: `posix_spawn` with attributes made at startup), then `nx2/crash_report.py` `build()` (what leaves the PC). The window is LongshotLauncher#46; both should merge together (the launcher's window says "This build's tools can't read crash reports yet" with builders from before this PR). - Folders built from a checkout get no `longshot_version.txt`, so no `builder=` line and no window. Releases have one. - The game writes `longshot_log.txt` only when it isn't started from a terminal. Started from a terminal, it logs there, and the window finds no crash to report. - Left for follow-ups on purpose, listed in the doc's Steps: - CI's `source=` line (the release flag); - `longshot_defaults.ini`; - the Crash reports row in the in-game menu and in `recomplauncher.json`; - the AGENTS.md rule. - The relay's side (the `release` field, `[not a release]`) is already deployed in Bean-Ops. ## Issues See #81 ## Checklist ### For everyone - [x] One topic. - [x] The title is a Conventional Commit (`type(scope): description`). - [x] Rebased on current `main`. - [x] `python -I -B tests/run_all.py` run and passing (any failure that also fails on `main` is named above). - [x] Tested in the game, or what wasn't is named under Testing. - [x] Testing filled in: what was tested, what wasn't, and what needs other testers (with test requests). - [x] Measured: main and this PR at named commits, on the same PC, scene and settings, at least 2 interleaved runs each, the median reported. - [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. (Both beans are Magic Bean's own art.) ### If applicable - [x] AI assistance recorded with `Assisted-by:` / `Co-Authored-By:` trailers. - [x] Flags noted, and the switch reference updated (docs/development.md, the README). - [x] Screenshots for a change that shows on screen. - [x] A game-free test added for the fix, failing on `main` and passing here. - [x] A check under `tests/` that reads changed source updated in this PR, not weakened. - [x] Host code builds on both Windows (llvm-mingw) and Linux, with UTF-8 paths. (After the third review: `hos_crash_window.c` again with llvm-mingw `-Werror`; the `rx_backend.c` change is one log line.) - [x] Docs updated. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_015KgSKoUqUvce5UXG89NBgi
gab9281 self-assigned this 2026-11-13 06:22:43 +00:00
gab9281 force-pushed feat/crash-report-window from 1f0e9dc5f5
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
PR description / Description follows the template (pull_request_target) Failing after 9s
CI / Conventional Commit title (pull_request) Failing after 7s
CI / Workflow lint (actionlint) (pull_request) Failing after 9s
Third-party parts / As their notes say (pull_request) Successful in 16s
to edfc1ef3e1
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
PR description / Description follows the template (pull_request_target) Failing after 9s
CI / Conventional Commit title (pull_request) Has been cancelled
CI / Workflow lint (actionlint) (pull_request) Has been cancelled
Third-party parts / As their notes say (pull_request) Has been cancelled
2026-11-13 06:42:09 +00:00
Compare
gab9281 force-pushed feat/crash-report-window from edfc1ef3e1
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
PR description / Description follows the template (pull_request_target) Failing after 9s
CI / Conventional Commit title (pull_request) Has been cancelled
CI / Workflow lint (actionlint) (pull_request) Has been cancelled
Third-party parts / As their notes say (pull_request) Has been cancelled
to 468fe236df
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
PR description / Description follows the template (pull_request_target) Failing after 8s
CI / Conventional Commit title (pull_request) Failing after 8s
CI / Workflow lint (actionlint) (pull_request) Failing after 10s
Third-party parts / As their notes say (pull_request) Successful in 15s
2026-11-13 06:42:20 +00:00
Compare
gab9281 force-pushed feat/crash-report-window from 468fe236df
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
PR description / Description follows the template (pull_request_target) Failing after 8s
CI / Conventional Commit title (pull_request) Failing after 8s
CI / Workflow lint (actionlint) (pull_request) Failing after 10s
Third-party parts / As their notes say (pull_request) Successful in 15s
to 752ff88994
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) Failing after 6s
CI / Workflow lint (actionlint) (pull_request) Failing after 10s
Third-party parts / As their notes say (pull_request) Successful in 13s
PR description / Description follows the template (pull_request_target) Failing after 7s
2026-11-13 06:52:13 +00:00
Compare
gab9281 force-pushed feat/crash-report-window from 752ff88994
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) Failing after 6s
CI / Workflow lint (actionlint) (pull_request) Failing after 10s
Third-party parts / As their notes say (pull_request) Successful in 13s
PR description / Description follows the template (pull_request_target) Failing after 7s
to 715cf050da
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) Failing after 6s
CI / Workflow lint (actionlint) (pull_request) Successful in 9s
Third-party parts / As their notes say (pull_request) Successful in 11s
PR description / Description follows the template (pull_request_target) Failing after 3s
2026-11-13 07:00:35 +00:00
Compare
gab9281 changed title from feat/crash-report-window to WIP: feat: a crash window that shows the crash report and sends it only on Send Help 2026-11-13 07:00:55 +00:00
Author
Member

Updated to 715cf050, rebased on main @ 39647e1a (a CI-only change).

  • The Sent page's icon is now assets/magicbean-report-sent.png, Magic Bean's own art, which replaces the earlier
    bean; the license box is ticked.
  • The Sent page says "Your crash report has been submitted to the team, thank you!" and its link opens the report's
    own comment on #81.
  • The report redacts the user name wherever it appears as a word, not only in home paths.
  • A window left open no longer drops the marker of a later crash, and a report the server couldn't receive is kept.
  • python -I -B tests/run_all.py: 477 passed, 0 failed, 64 skipped.
  • New screenshots in the description, from real crashes on Linux and on Windows under Wine.

🤖 Generated with Claude Code

Updated to 715cf050, rebased on main @ 39647e1a (a CI-only change). - The Sent page's icon is now `assets/magicbean-report-sent.png`, Magic Bean's own art, which replaces the earlier bean; the license box is ticked. - The Sent page says "Your crash report has been submitted to the team, thank you!" and its link opens the report's own comment on #81. - The report redacts the user name wherever it appears as a word, not only in home paths. - A window left open no longer drops the marker of a later crash, and a report the server couldn't receive is kept. - `python -I -B tests/run_all.py`: 477 passed, 0 failed, 64 skipped. - New screenshots in the description, from real crashes on Linux and on Windows under Wine. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
gab9281 changed title from WIP: feat: a crash window that shows the crash report and sends it only on Send Help to feat: a crash window that shows the crash report and sends it only on Send Help 2026-11-13 07:02:26 +00:00
gab9281 force-pushed feat/crash-report-window from 715cf050da
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) Failing after 6s
CI / Workflow lint (actionlint) (pull_request) Successful in 9s
Third-party parts / As their notes say (pull_request) Successful in 11s
PR description / Description follows the template (pull_request_target) Failing after 3s
to bb847be7c1
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 3s
CI / Workflow lint (actionlint) (pull_request) Successful in 4s
Third-party parts / As their notes say (pull_request) Successful in 5s
PR description / Description follows the template (pull_request_target) Failing after 7s
2026-11-13 08:12:58 +00:00
Compare
Author
Member

Updated to bb847be7, rebased on main @ 6b7fa356 (#148).

  • A /code-review (high) of the whole PR found 10 things, all fixed:
    • send() keeps the report when the answer can't be read.
    • A marker cut short still counts.
    • The OS and CPU come from the machine when the log has no line for them.
    • The WebGPU log line now says why it was picked.
    • LONGSHOT_NO_CRASH_WINDOW follows env_on.
    • Device names (microphone, audio output) are kept out of the log lines.
    • The user name is matched case-sensitively (a Steam Deck's user is deck).
    • Linux builder:
      • --launch with a crash waiting goes on to the launcher page.
      • With no crash waiting, the window is no longer destroyed inside __init__.
      • The Sent page's Close goes on as on Qt.
      • The always-send timer is cancelled once the player answers.
    • The window's rows and Show texts are shared in nx2/crash_report.py.
  • The relay's address and #81's address moved out of the code into nx2/addresses.ini.
  • Measured (table above):
    • Startup and play are unchanged.
    • A crash has the builder running in about 0.1 s.
  • run_all: 522 passed, 0 failed.
  • Test requests opened: T-YFML (Windows, not Wine) and T-FTCD (AMD/Intel GPUs). They need the testing wanted label before anyone can claim them.
Updated to bb847be7, rebased on main @ 6b7fa356 (#148). - A `/code-review` (high) of the whole PR found 10 things, all fixed: - `send()` keeps the report when the answer can't be read. - A marker cut short still counts. - The OS and CPU come from the machine when the log has no line for them. - The WebGPU log line now says why it was picked. - `LONGSHOT_NO_CRASH_WINDOW` follows `env_on`. - Device names (microphone, audio output) are kept out of the log lines. - The user name is matched case-sensitively (a Steam Deck's user is `deck`). - Linux builder: - `--launch` with a crash waiting goes on to the launcher page. - With no crash waiting, the window is no longer destroyed inside `__init__`. - The Sent page's Close goes on as on Qt. - The always-send timer is cancelled once the player answers. - The window's rows and Show texts are shared in `nx2/crash_report.py`. - The relay's address and #81's address moved out of the code into `nx2/addresses.ini`. - Measured (table above): - Startup and play are unchanged. - A crash has the builder running in about 0.1 s. - `run_all`: 522 passed, 0 failed. - Test requests opened: T-YFML (Windows, not Wine) and T-FTCD (AMD/Intel GPUs). They need the `testing wanted` label before anyone can claim them.
Author
Member

Rebased on main @ 36a12f6f (#161 removed the builders' self-update: the crash window's lines kept on top of it, in the Linux builder's docstring, Builder() and the launcher page, and the Qt start's docstring). run_all: 520 passed, 1 failed (test_prepare_cache_save, which fails the same on main @ 36a12f6f). The Measurements' main and PR lines added.

Rebased on main @ 36a12f6f (#161 removed the builders' self-update: the crash window's lines kept on top of it, in the Linux builder's docstring, Builder() and the launcher page, and the Qt start's docstring). run_all: 520 passed, 1 failed (test_prepare_cache_save, which fails the same on main @ 36a12f6f). The Measurements' main and PR lines added.
gab9281 force-pushed feat/crash-report-window from bb847be7c1
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 3s
CI / Workflow lint (actionlint) (pull_request) Successful in 4s
Third-party parts / As their notes say (pull_request) Successful in 5s
PR description / Description follows the template (pull_request_target) Failing after 7s
to 77609f451d
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
Website / Website on Cloudflare (pull_request) Has been skipped
PR description / Description follows the template (pull_request_target) Successful in 3s
CI / Conventional Commit title (pull_request) Successful in 5s
CI / Workflow lint (actionlint) (pull_request) Successful in 4s
Third-party parts / As their notes say (pull_request) Successful in 5s
2026-11-13 08:28:14 +00:00
Compare
fix(crash): the second review's findings on the crash window
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 3s
CI / Workflow lint (actionlint) (pull_request) Successful in 5s
Third-party parts / As their notes say (pull_request) Successful in 6s
PR description / Description follows the template (pull_request_target) Successful in 3s
6c9b7e7ab9
- send(): a 2xx is sent, whatever its body: never kept, never sent
  again (a report the relay posted was offered again before).
- Linux window: closing it with the title bar's X, started from the
  launcher or Play, goes on to them (as Qt's closeEvent); a Send Help
  pressed before the always-send tick's cancels that one, and a refused
  report's disabled Send isn't sent by Enter; the Sent page from the
  launcher has no Play again (Close goes back to it, as on Qt).
- nx2/addresses.ini: a BOM or a file that doesn't parse turns the uses
  off instead of stopping the builders.
- A microphone picked earlier in the session never reaches the log
  lines ("audio in: <name> open/is back/...").
- The user name is matched in any case again, except a name that is
  also a word of the report's lines (deck, arch, ...), in its own case.
- this_cpu() and this_os() word the line as the game does (the RAM, the
  desktop and session, the AppImage, Wine).
- The windows say where reports go, and the Sent link's number, from
  crash_issue; without it, no place and no link.
- Qt pending(): no tools lookup when there is no folder.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015KgSKoUqUvce5UXG89NBgi
Author
Member

Updated to 6c9b7e7a: a second /code-review (high) of the follow-ups found 10 things. 8 fixed: a 2xx is sent whatever its body (no duplicate on #81); the Linux window's X goes on to the launcher or Play as on Qt; the always-send timer is cancelled by a pressed Send Help, and a refused report isn't sent by Enter; no Play again on the Sent page from the launcher; addresses.ini with a BOM or broken never stops the builders; earlier microphones redacted from 'audio in:' lines; the user name in any case again except words of the report's lines (deck, arch); this_cpu/this_os word the line as the game does; the windows' issue text from crash_issue. Left: the logs read twice at --launch with a crash waiting (a few small files), and pipeline_share's own #14 address (main's code, not this PR's). run_all: 520 passed, 1 failed (test_prepare_cache_save, as on main).

Updated to 6c9b7e7a: a second /code-review (high) of the follow-ups found 10 things. 8 fixed: a 2xx is sent whatever its body (no duplicate on #81); the Linux window's X goes on to the launcher or Play as on Qt; the always-send timer is cancelled by a pressed Send Help, and a refused report isn't sent by Enter; no Play again on the Sent page from the launcher; addresses.ini with a BOM or broken never stops the builders; earlier microphones redacted from 'audio in:' lines; the user name in any case again except words of the report's lines (deck, arch); this_cpu/this_os word the line as the game does; the windows' issue text from crash_issue. Left: the logs read twice at --launch with a crash waiting (a few small files), and pipeline_share's own #14 address (main's code, not this PR's). run_all: 520 passed, 1 failed (test_prepare_cache_save, as on main).
feat(crash): name the place before the first map
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 3s
PR description / Description follows the template (pull_request_target) Successful in 4s
CI / Workflow lint (actionlint) (pull_request) Successful in 5s
Third-party parts / As their notes say (pull_request) Successful in 6s
0d8cc5b5ba
A crash before any map: line said "not known". The report now says
"Preparing shaders" from the shaders' first progress line until "the
shaders took", and "Title and intro" otherwise: the same names as the
performance summary's (#206).

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_015KgSKoUqUvce5UXG89NBgi
HumanBean left a comment

The privacy design holds up. Nothing is sent without Send Help or the opt-in "Always send" tick. The payload is exactly what the window shows. Register and memory rows, bindings, the microphone name and the hardware hashes stay out. The handler does no allocation for its own part. The send runs on a thread, so the window stays responsive. This can't merge as it is, though.

Blocking: the windows no longer exist on main. This branch forks from 36a12f6f. Since then main has removed both windows the crash UI lives in:

  • #171 removed builder/py (the Qt builder: crash.py, window.py, longshot_builder_start.py).
  • #181 removed builder/linux/longshot_builder.py (the Tk window) and made the Builder command-line only.

On main, AppRun and LongshotBuilder.exe answer any other argument with the "get the launcher" message and exit 1. So <builder> --crash <folder> from hos_crash_window.c would show that message box instead of a report. The window needs a new home, most likely the Longshot Launcher (or a small standalone window shipped with the release). hos_crash_window.c should start that. The "Rebased on current main" box is not true now.

What carries over as is: nx2/crash_report.py, tests/test_crash_report.py, the report: log lines and most of the host side. Two fixes for those parts, inline:

  1. The process started from the signal handler inherits a blocked SIGSEGV. on_driver_fault (gpu/vk/vk_draw.cpp) has no SA_NODEFER, and every Linux Vulkan run installs it. execve keeps the signal mask. Whatever the window starts later, like Play again or the launcher, inherits it too. A game started that way loses its crash handler: the kernel kills it on the next fault, with no CRASH: lines and no report. Clear the mask in the child.
  2. Wine and Proton paths aren't redacted. Z:\home\<name>\… matches none of the patterns in HOMES, and the user-name pass only knows the Wine prefix's steamuser. The PR's own Windows test ran under Wine.

Smaller points:

  • The X button is ignored while a send runs. timeout=10 limits each socket step, not DNS or the whole send, so a bad network can leave the window impossible to close for longer than 10 s.
  • Reports go to data.magicbean.gmatte.xyz first. Is that the long-term host? It's the place players' IPs and reports land, and the window names only git.magicbean.dev.

I didn't build this or run the tests. This review comes from reading the code. I also ran redact() by itself on sample paths to confirm the Wine gap.

The privacy design holds up. Nothing is sent without Send Help or the opt-in "Always send" tick. The payload is exactly what the window shows. Register and memory rows, bindings, the microphone name and the hardware hashes stay out. The handler does no allocation for its own part. The send runs on a thread, so the window stays responsive. This can't merge as it is, though. **Blocking: the windows no longer exist on main.** This branch forks from 36a12f6f. Since then main has removed both windows the crash UI lives in: - https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/171 removed `builder/py` (the Qt builder: `crash.py`, `window.py`, `longshot_builder_start.py`). - https://git.magicbean.dev/MagicBeanDev/Longshot/pulls/181 removed `builder/linux/longshot_builder.py` (the Tk window) and made the Builder command-line only. On main, `AppRun` and `LongshotBuilder.exe` answer any other argument with the "get the launcher" message and exit 1. So `<builder> --crash <folder>` from `hos_crash_window.c` would show that message box instead of a report. The window needs a new home, most likely the Longshot Launcher (or a small standalone window shipped with the release). `hos_crash_window.c` should start that. The "Rebased on current main" box is not true now. What carries over as is: `nx2/crash_report.py`, `tests/test_crash_report.py`, the `report:` log lines and most of the host side. Two fixes for those parts, inline: 1. **The process started from the signal handler inherits a blocked SIGSEGV.** `on_driver_fault` (`gpu/vk/vk_draw.cpp`) has no `SA_NODEFER`, and every Linux Vulkan run installs it. `execve` keeps the signal mask. Whatever the window starts later, like Play again or the launcher, inherits it too. A game started that way loses its crash handler: the kernel kills it on the next fault, with no `CRASH:` lines and no report. Clear the mask in the child. 2. **Wine and Proton paths aren't redacted.** `Z:\home\<name>\…` matches none of the patterns in `HOMES`, and the user-name pass only knows the Wine prefix's `steamuser`. The PR's own Windows test ran under Wine. Smaller points: - The X button is ignored while a send runs. `timeout=10` limits each socket step, not DNS or the whole send, so a bad network can leave the window impossible to close for longer than 10 s. - Reports go to `data.magicbean.gmatte.xyz` first. Is that the long-term host? It's the place players' IPs and reports land, and the window names only git.magicbean.dev. I didn't build this or run the tests. This review comes from reading the code. I also ran `redact()` by itself on sample paths to confirm the Wine gap.
@ -0,0 +173,4 @@
setsid(); /* (its own session: it outlives this process, and a terminal's Ctrl+C doesn't reach it) */
const int null = open("/dev/null", O_RDWR);
if (null >= 0) dup2(null, 0), dup2(null, 1), dup2(null, 2);
execve(builder, window_argv, environ);
Owner

The child inherits the crashing thread's signal mask, and execve keeps it. On Linux every Vulkan run installs on_driver_fault (gpu/vk/vk_draw.cpp, install_driver_fault_guard) with SA_SIGINFO but no SA_NODEFER, so a crash reaches report_crash with SIGSEGV (or SIGBUS) blocked. The window then starts with it blocked, and so does anything it starts: Play again's game, or the launcher. A game started that way has its crash handler skipped: a fault in it is delivered with SIG_DFL, so it dies with no CRASH: lines and no report. Clear the mask before execve. sigprocmask is async-signal-safe:

sigset_t none;
sigemptyset(&none);
sigprocmask(SIG_SETMASK, &none, NULL);

While you're here: the game's fds without O_CLOEXEC (the log, sockets, the GPU device) also pass to the window, which outlives the game. close_range(3, ~0U, 0) in the child would drop them (with a fallback where it's missing).

The child inherits the crashing thread's signal mask, and `execve` keeps it. On Linux every Vulkan run installs `on_driver_fault` (`gpu/vk/vk_draw.cpp`, `install_driver_fault_guard`) with `SA_SIGINFO` but no `SA_NODEFER`, so a crash reaches `report_crash` with SIGSEGV (or SIGBUS) blocked. The window then starts with it blocked, and so does anything it starts: Play again's game, or the launcher. A game started that way has its crash handler skipped: a fault in it is delivered with SIG_DFL, so it dies with no `CRASH:` lines and no report. Clear the mask before `execve`. `sigprocmask` is async-signal-safe: ```c sigset_t none; sigemptyset(&none); sigprocmask(SIG_SETMASK, &none, NULL); ``` While you're here: the game's fds without `O_CLOEXEC` (the log, sockets, the GPU device) also pass to the window, which outlives the game. `close_range(3, ~0U, 0)` in the child would drop them (with a fallback where it's missing).
@ -0,0 +3,4 @@
# isn't sent and the window says so (it is kept and offered again).
[addresses]
# The crash relay (Bean-Ops deploy/crash-relay): POST a crash report
crash_reports = https://data.magicbean.gmatte.xyz/crash
Owner

Question: is data.magicbean.gmatte.xyz the long-term host for the relay? It's where players' reports and IP addresses go first, and the window only names git.magicbean.dev. If it's going to move to a magicbean.dev name, it's better to do it before the first release that sends.

Question: is `data.magicbean.gmatte.xyz` the long-term host for the relay? It's where players' reports and IP addresses go first, and the window only names git.magicbean.dev. If it's going to move to a magicbean.dev name, it's better to do it before the first release that sends.
@ -0,0 +86,4 @@
NOT_SENT = re.compile(r"^(key_|pad_)|^(microphone_device|auto_for|auto_tuned)$")
HOMES = [(re.compile(r"/home/[^/\s]+"), "~"), (re.compile(r"/var/home/[^/\s]+"), "~"), (re.compile(r"/Users/[^/\s]+"), "~"),
(re.compile(r"[A-Za-z]:[\\/]+Users[\\/]+[^\\/\s]+", re.I), "%USERPROFILE%")]
Owner

Under Wine or Proton, which is how the PR's Windows test ran, home is the prefix's C:\users\steamuser. But the game folder is often on the Linux side, Z:\home\<name>\Games\Longshot. None of the patterns match \home\ with backslashes, and the user-name pass only knows steamuser. So redact(r"Z:\home\gabriel\Games\Longshot\bin", "C:\\Users\\steamuser", "steamuser") returns the path with gabriel still in it. Add a drive-letter home pattern (e.g. [A-Za-z]:[\\/]+home[\\/]+[^\\/\s]+ → ~), and add the case to tests/test_crash_report.py.

Under Wine or Proton, which is how the PR's Windows test ran, home is the prefix's `C:\users\steamuser`. But the game folder is often on the Linux side, `Z:\home\<name>\Games\Longshot`. None of the patterns match `\home\` with backslashes, and the user-name pass only knows `steamuser`. So `redact(r"Z:\home\gabriel\Games\Longshot\bin", "C:\\Users\\steamuser", "steamuser")` returns the path with `gabriel` still in it. Add a drive-letter `home` pattern (e.g. `[A-Za-z]:[\\/]+home[\\/]+[^\\/\s]+` → `~`), and add the case to `tests/test_crash_report.py`.
@ -0,0 +448,4 @@
req = urllib.request.Request(url, data=json.dumps(report).encode(), method="POST",
headers={"Content-Type": "application/json", "User-Agent": "longshot-crash-report"})
try:
with urllib.request.urlopen(req, timeout=timeout) as resp:
Owner

Nit: timeout=10 limits each socket operation, not the whole send. DNS isn't covered, and a slow answer resets the timer on each read. Both windows ignore close while busy, so on a bad network the player can't close the window for longer than 10 s. Since the send thread is a daemon, closing while busy could just keep the report (like a failed send) instead of being refused.

Nit: `timeout=10` limits each socket operation, not the whole send. DNS isn't covered, and a slow answer resets the timer on each read. Both windows ignore close while `busy`, so on a bad network the player can't close the window for longer than 10 s. Since the send thread is a daemon, closing while busy could just keep the report (like a failed send) instead of being refused.
gab9281 force-pushed feat/crash-report-window from 0d8cc5b5ba
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 3s
PR description / Description follows the template (pull_request_target) Successful in 4s
CI / Workflow lint (actionlint) (pull_request) Successful in 5s
Third-party parts / As their notes say (pull_request) Successful in 6s
to 8cdc358d1f
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
Website / Website on Cloudflare (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 33s
CI / Workflow lint (actionlint) (pull_request) Successful in 40s
Third-party parts / As their notes say (pull_request) Successful in 36s
PR description / Description follows the template (pull_request_target) Successful in 3s
2026-11-13 17:15:51 +00:00
Compare
gab9281 changed title from feat: a crash window that shows the crash report and sends it only on Send Help to WIP: feat: a crash window that shows the crash report and sends it only on Send Help 2026-11-13 17:19:07 +00:00
gab9281 force-pushed feat/crash-report-window from 8cdc358d1f
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
Website / Website on Cloudflare (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 33s
CI / Workflow lint (actionlint) (pull_request) Successful in 40s
Third-party parts / As their notes say (pull_request) Successful in 36s
PR description / Description follows the template (pull_request_target) Successful in 3s
to 25ee9472ae
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 7s
Third-party parts / As their notes say (pull_request) Successful in 9s
PR description / Description follows the template (pull_request_target) Successful in 3s
2026-11-13 17:56:16 +00:00
Compare
Author
Member

Thanks for the review. Each point, in the order you raised them:

Blocking: the windows no longer exist on main. The window is now the Longshot Launcher's: LongshotLauncher#46, --crash <folder>. The game already starts builder=, and the launcher writes itself there (LONGSHOT_BUILDER_EXE), so host/hos_crash_window.c starts it with no change to how it finds it. This PR is rebased on main (main @ fbd42024, at 25ee9472). The builder windows, their test and the beans (now in the launcher) are gone. What stays here is what you listed as carrying over: nx2/crash_report.py, its tests, the report: lines and the host side.

The report stays one implementation. The launcher runs this version's nx2/crash_report.py with that version's Python: --window for what to show (one line of JSON, nothing sent), then --send --json --time <t> after Send Help. The folders that Longshot's own command-line builders make name them as builder=, so the AppImage and LongshotBuilder.exe now answer --crash with nothing. A crash no longer shows their "get the launcher" box; the marker waits for the launcher's next start.

1. Signal mask. Fixed, and taken further after a second review: the window now starts with posix_spawn, its attributes made at startup. Every signal is back to its default and none is blocked, it gets a session of its own, its output goes to /dev/null, and every fd above 2 is closed (addclosefrom_np, whatever the fd limit). The game's handlers can't run in the child either, which they could between unblocking and execve in the vfork version. tests/test_crash_window.py checks it from a stand-in launcher: SigBlk without SIGSEGV, SigIgn without the game's SIGUSR1, and none of the game's files. Those checks fail on the previous code. In the game: SIGSEGV at 25 s had the launcher's --crash running 0.11 s later with SigBlk: 0000000000000000 and none of the game's files open (at 25ee9472, with SigIgn only the launcher's own SIGPIPE).

2. Wine and Proton paths. Z:\home\<name> and z:/var/home/<name> become ~, and your example is now in tests/test_crash_report.py. A drive has to be a letter on its own, so file:///home/<name> keeps its file://. While there: /var/home/<name> used to come out as /var~, because /home/ matched first. The more specific patterns now go first.

Close during a send. The send is a process of its own, so the window closes at once and the send finishes without it. The report is kept unless it was sent or refused. A send is also only of the crash the window showed: if another crash took the marker meanwhile, nothing is sent and that crash gets its own window.

The relay's host. data.magicbean.gmatte.xyz stays for now. It is only in nx2/addresses.ini, so moving it is a one-line change, and the doc's open questions now say to do it before the first release that sends.

"Rebased on current main" is true again. #141 and LongshotLauncher#46 should merge together.

Thanks for the review. Each point, in the order you raised them: **Blocking: the windows no longer exist on main.** The window is now the Longshot Launcher's: [LongshotLauncher#46](https://git.magicbean.dev/MagicBeanDev/LongshotLauncher/pulls/46), `--crash <folder>`. The game already starts `builder=`, and the launcher writes itself there (`LONGSHOT_BUILDER_EXE`), so `host/hos_crash_window.c` starts it with no change to how it finds it. This PR is rebased on main (main @ fbd42024, at 25ee9472). The builder windows, their test and the beans (now in the launcher) are gone. What stays here is what you listed as carrying over: `nx2/crash_report.py`, its tests, the `report:` lines and the host side. The report stays one implementation. The launcher runs this version's `nx2/crash_report.py` with that version's Python: `--window` for what to show (one line of JSON, nothing sent), then `--send --json --time <t>` after Send Help. The folders that Longshot's own command-line builders make name them as `builder=`, so the AppImage and `LongshotBuilder.exe` now answer `--crash` with nothing. A crash no longer shows their "get the launcher" box; the marker waits for the launcher's next start. **1. Signal mask.** Fixed, and taken further after a second review: the window now starts with `posix_spawn`, its attributes made at startup. Every signal is back to its default and none is blocked, it gets a session of its own, its output goes to `/dev/null`, and every fd above 2 is closed (`addclosefrom_np`, whatever the fd limit). The game's handlers can't run in the child either, which they could between unblocking and `execve` in the vfork version. `tests/test_crash_window.py` checks it from a stand-in launcher: `SigBlk` without SIGSEGV, `SigIgn` without the game's SIGUSR1, and none of the game's files. Those checks fail on the previous code. In the game: SIGSEGV at 25 s had the launcher's `--crash` running 0.11 s later with `SigBlk: 0000000000000000` and none of the game's files open (at 25ee9472, with `SigIgn` only the launcher's own SIGPIPE). **2. Wine and Proton paths.** `Z:\home\<name>` and `z:/var/home/<name>` become `~`, and your example is now in `tests/test_crash_report.py`. A drive has to be a letter on its own, so `file:///home/<name>` keeps its `file://`. While there: `/var/home/<name>` used to come out as `/var~`, because `/home/` matched first. The more specific patterns now go first. **Close during a send.** The send is a process of its own, so the window closes at once and the send finishes without it. The report is kept unless it was sent or refused. A send is also only of the crash the window showed: if another crash took the marker meanwhile, nothing is sent and that crash gets its own window. **The relay's host.** `data.magicbean.gmatte.xyz` stays for now. It is only in `nx2/addresses.ini`, so moving it is a one-line change, and the doc's open questions now say to do it before the first release that sends. "Rebased on current main" is true again. #141 and LongshotLauncher#46 should merge together.
gab9281 changed title from WIP: feat: a crash window that shows the crash report and sends it only on Send Help to feat: a crash window that shows the crash report and sends it only on Send Help 2026-11-13 17:56:42 +00:00
gab9281 force-pushed feat/crash-report-window from 25ee9472ae
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 7s
Third-party parts / As their notes say (pull_request) Successful in 9s
PR description / Description follows the template (pull_request_target) Successful in 3s
to 11684c08e7
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
Website / Website on Cloudflare (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 6s
CI / Workflow lint (actionlint) (pull_request) Successful in 8s
Third-party parts / As their notes say (pull_request) Successful in 11s
PR description / Description follows the template (pull_request_target) Successful in 5s
2026-11-13 19:36:52 +00:00
Compare
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
Website / Website on Cloudflare (pull_request) Has been skipped
CI / Conventional Commit title (pull_request) Successful in 6s
CI / Workflow lint (actionlint) (pull_request) Successful in 8s
Third-party parts / As their notes say (pull_request) Successful in 11s
PR description / Description follows the template (pull_request_target) Successful in 5s
This pull request doesn't have enough approvals yet. 0 of 1 approvals granted.
You are not authorized to merge this pull request.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u 'feat/crash-report-window:gab9281-feat/crash-report-window'
git switch 'gab9281-feat/crash-report-window'
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
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!141
No description provided.