fix(builder): Linux Play leaves the game its hookshot_log.txt again #2279
No reviewers
Labels
No labels
accessibility
autorelease: pending
autorelease: tagged
bug
dependencies
documentation
duplicate
enhancement
github_actions
good first issue
help wanted
invalid
question
testing wanted
wontfix
No milestone
No assignees
1 participant
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Hookshot!2279
Loading…
Reference in a new issue
No description provided.
Delete branch "builder/play-keeps-log"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Since #2024, a Linux player who starts the game with Play (in the Builder after a build, or in the launcher that the Hookshot menu entry and
hookshotin the game folder open) gets no newhookshot_log.txt: the game writes its log into #2024's capture instead, which keeps the first 64 KiB inhookshot_startup.logonce the game has closed and throws the rest away. The log every issue asks for is missing, or an older run's log is still there and gets sent in its place.Fixes #2330.
Why it happened
play-diagnostics.shruns the game as"$1" 2>&1 >/dev/null | head -c 65536 ..., so the game's stderr is a pipe. The game openshookshot_log.txtonly when its stderr is nowhere to log (host/hos_main.c:GetFileType(err) == FILE_TYPE_UNKNOWN), and the POSIX shim answersFILE_TYPE_PIPEfor a pipe (host/posix/win32_posix.cGetFileType): to the game, a pipe means someone reads its output, as with./bin/hookshot | tee. Before #2024, Play gave the game/dev/null, which counts as nowhere, so it wrote its log.What changed
builder/linux/hookshot_builder.pyplay(): the game's stderr is one end of asocket.socketpair(), and the script reads the other end on its stdin. The shim counts a socket as nowhere to log (it says so for the journal's socket a desktop launcher gives), so the game writeshookshot_log.txtand keepshookshot_log.1.txtto.4.txt, as before #2024.builder/linux/play-diagnostics.sh: reads the socket instead of a pipe. What reaches it is what the game prints before its log opens. On Linux the display start comes first, sodisplay: SDL_Init: ...: no windowand the loader's errors land there, along with the udmabuf warning the game already mirrors there.hookshot_startup.logkeeps that, the 64 KiB cap and the exit status, as #2024 made it. The game's stdin is now/dev/nullinstead of the Builder's. If the script cannot make its temporary folder, the game gets/dev/nullfor stderr, as before #2024.Hookshot.AppImage --play(hookshot-launch) never went through the script and is unchanged.Why a socket and not a change in the game (a marker that makes it log even with a launcher's pipe): every game folder would need a new build, and a folder built before the change, started from a newer Builder, would still have no log. The script also cannot know in advance whether the game will fail early, so it cannot pick between the two set-ups.
How it was tested
tests/test_linux_play_diagnostics.pykeeps #2024's checks and adds one. Play starts a small game compiled with the shim's ownGetFileType(cut fromhost/posix/win32_posix.c). It prints a line, openshookshot_log.txtonly whenGetFileType(stderr)isFILE_TYPE_UNKNOWNashost/hos_main.cdoes (the test checks that this condition is still there), and logs 16,384 lines, more than 64 KiB. Onmainthe check fails (no hookshot_log.txt). With this change the whole log is inhookshot_log.txt, andhookshot_startup.logholds the first line and the exit status. It was also run with dash 0.5.12 as the shell (Debian's and Ubuntu's/bin/sh): it passes, and fails onmainthe same way.python -I -B tests/run_all.pyon this branch as opened (77a91124, onmain49f75dc8): 362 passed, 2 failed, 10 skipped. The 2 failures also fail onmainon this machine, for reasons outside this change:tests/test_updater_asset_digest.py(its fixture needs the .NET 8 SDK, and only .NET 10 is installed) andtests/test_wayland_libdecor_hint.py(a clang 23 warning under-Werror).The real game: a build of
main(bdfe09c, with the same log set-up asmaintoday) on an RTX 4090 with NVIDIA 615.78.08 on Arch Linux. The folder was laid out as the Builder lays it out (bin/hookshot), and each start went through the Builder's ownplay(), imported fromhookshot_builder.pyof each revision. The runs were headless and muted, and the game was ended with SIGTERM after 30 s (as closing it would):hookshot_log.txthookshot_startup.log2009c5f)/dev/nullmainvk: 600 presentsreport) andHookshot exited with status 143vi: 1037 frames presented; the run before's log moved tohookshot_log.1.txtHookshot exited with status 143A start that fails before the log opens (no display reachable, so SDL's video start fails in
mainbefore the log is set up):mainand this branch leave the same 175-bytehookshot_startup.log(display: SDL_Init: wayland,x11 not available: no windowandHookshot exited with status 1), and nohookshot_log.txt. What #2024 added for this case is kept.A start that fails after the log has opened: an empty
image.bin, whichplay()still starts. The game logs its first lines, then stops withimage.bin: 0 bytes, the lifted code expects 0x6020000: lift and image do not match, exit 1. Withmain's Play these three lines and the exit status are inhookshot_startup.log, and nohookshot_log.txtis written. With this change they are inhookshot_log.txt(274 bytes, the message is its last line), andhookshot_startup.logholds onlyHookshot exited with status 1.Not tested
play()the button calls.socketis part of every CPython, including the AppImage's python-build-standalone 3.12 (the version these runs used).#1958 (profiles) changes two lines of the same test file. Whichever merges second needs a small rebase there.
Assisted-by: Claude Opus 5.5
Co-Authored-By: Claude Opus 5.5 [email protected]
Looks good. I read the Linux Play path and the script's fd handling:
play()hands the script a socketpair (stdin = reading end, stderr = writing end). The script moves stdin to fd 3 for a backgroundhead | catreader, closes its own copy, and runs the game with stdout to /dev/null, stdin /dev/null and stderr the socket.GetFileTypeon main returns FILE_TYPE_UNKNOWN for a socket (the case its comment already names for the journal's socket), so the game openshookshot_log.txtas before #2024.exec 2>/dev/nullafter the game exits before waiting. The Python side closes both ends after Popen, and the mktemp-failure fallback gives the game /dev/null as before.GetFileTypeout of the shim and checks that hos_main.c still has the condition, so it will notice if either side drifts.{cxx}is a supported RUN_ALL_ARGS placeholder.The PR text claims match what I see in the diff. Real-game numbers are the author's own run on Arch, so I did not rerun them. The Linux AppImage, Windows builder and Game-free source regression workflows show
action_required(first-time contributor approval), so they have not run yet. Someone with rights needs to approve them before merging. Only the title check has run, and it passes.Thanks for the careful read. About the workflows: GitHub now starts them for my pushes without an approval (it did for #2380). Main's game-free tests fail until #2380 is in, so I'll rebase this onto main right after that merge and push; the Linux AppImage, Windows builder and game-free runs should then show here on their own.
github-sync referenced this pull request2026-11-12 06:22:54 +00:00