fix: Linux launch crash on glibc 2.42+ (bypass jpackage native launcher)#685
fix: Linux launch crash on glibc 2.42+ (bypass jpackage native launcher)#685rainxchzed wants to merge 1 commit into
Conversation
WalkthroughLinux desktop crashes on startup when glibc >= 2.42 due to a broken jpackage native launcher. This PR fixes the issue by introducing a JVM-direct launcher script that parses jpackage app config and executes the bundled Java directly, then integrates this launcher into all Linux package formats (AppImage, Debian, RPM, Arch) and documents the fix in release notes. ChangesLinux launcher wrapper and packaging integration
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Greptile SummarySidesteps the glibc 2.42+ crash in the jpackage native launcher by introducing a pure-bash wrapper (
Confidence Score: 3/5Safe to merge for the happy path (single .cfg file, classpath mode), but the launcher can silently exit with no message when find returns multiple .cfg files under pipefail — swapping one bad failure mode for another. The core approach is sound and mirrors what the Flatpak build already does in production. The CI wiring across deb/rpm/AppImage/Arch is well-structured. However, the packaging/linux/github-store-launcher.sh — specifically line 31 and the module-path exec branch at lines 84–87. Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[User launches GitHub Store] --> B{Install type?}
B -->|AppImage| C[AppRun]
B -->|deb or rpm| D[bin/GitHub-Store as shell script]
B -->|Arch| E[usr/bin/github-store symlink]
C --> F[github-store-launcher.sh]
D --> F
E --> F
F --> G[Locate lib/runtime/bin/java]
G --> H{JVM found?}
H -->|No| I[Error: bundled JVM not found]
H -->|Yes| J[find lib/app/.cfg]
J --> K{CFG found?}
K -->|No| L[Error: cfg not found]
K -->|Yes| M[Parse cfg: classpath mainclass javaopts]
M --> N{app.mainmodule set?}
N -->|Yes| O[exec java with module-path]
N -->|No| P{classpath and mainclass set?}
P -->|Yes| Q[exec java with -cp classpath mainclass]
P -->|No| R[Error: cannot resolve entry point]
Reviews (1): Last reviewed commit: "fix: bypass jpackage native launcher tha..." | Re-trigger Greptile |
| exit 1 | ||
| fi | ||
|
|
||
| CFG="$(find "$APPDIR" -maxdepth 1 -name '*.cfg' 2>/dev/null | head -n1)" |
There was a problem hiding this comment.
Silent crash when multiple
.cfg files exist
find … | head -n1 under set -eo pipefail will SIGPIPE-kill find whenever two or more .cfg files are present in lib/app. head exits after the first line, find receives SIGPIPE and exits with status 141, pipefail propagates that non-zero status through the command substitution, and set -e terminates the script with no diagnostic message — replacing a segfault with a silent, unattributed crash that's harder to debug. The parallel code in the deb-patch step (workflow line 389) avoids this correctly with || true. The same guard is needed here.
| CFG="$(find "$APPDIR" -maxdepth 1 -name '*.cfg' 2>/dev/null | head -n1)" | |
| CFG="$(find "$APPDIR" -maxdepth 1 -name '*.cfg' 2>/dev/null | head -n1 || true)" |
| if [ -n "$mainmodule" ]; then | ||
| exec "$JAVA" "${javaopts[@]}" \ | ||
| ${modulepath:+--module-path "$modulepath"} \ | ||
| -m "$mainmodule" "$@" |
There was a problem hiding this comment.
Module-path branch silently drops classpath
When app.mainmodule is present in the cfg alongside app.classpath (a valid jpackage layout for split-module apps where some dependencies remain on the classpath), the exec on lines 85–87 omits -cp "$classpath" entirely. Java won't find any classpath-only classes, producing a ClassNotFoundException or NoClassDefFoundError at runtime with no hint as to why. The current Compose Desktop app uses classpath-mode so this branch is inactive for now, but the script is presented as a generic jpackage cfg reader and the gap is silent. Adding ${classpath:+-cp "$classpath"} to the module branch would close it.
|
|
||
| if [ -n "$mainmodule" ]; then | ||
| exec "$JAVA" "${javaopts[@]}" \ | ||
| ${modulepath:+--module-path "$modulepath"} \ |
There was a problem hiding this comment.
Unquoted conditional expansion may word-split module path
${modulepath:+--module-path "$modulepath"} is used unquoted. In bash, the inner double-quotes within a brace expansion are treated as literal characters at the point the full result is subject to word-splitting. If $modulepath contains spaces (uncommon but possible for JAR paths in custom install prefixes), the value will be incorrectly split into multiple arguments. Wrapping the whole expansion or using an array accumulator for the module-path argument would make this safe regardless of path contents.
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/build-desktop-platforms.yml:
- Around line 412-434: The workflow currently continues and may publish original
RPMs when patching fails; change the failure branches so the job exits non‑zero
instead of continuing. Specifically, replace the apt-get failure path that
currently does "echo ...; exit 0" with a non‑zero exit (e.g. exit 1) and
similarly change the rpmrebuild failure branches (the else blocks that echo
warnings after rpmrebuild or when "$rebuilt" is empty) to fail the job (exit 1)
rather than logging and continuing; ensure you also do not mv the original file
when rpmrebuild produced no output (the mv inside the success branch should
remain the only place originals are overwritten). Use the WRAPPER, OUTDIR,
rpmrebuild invocation, and the "$rebuilt" check to locate the exact places to
modify.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: fcb9fd13-e2de-401f-988c-227342bd793d
📒 Files selected for processing (15)
.github/workflows/build-desktop-platforms.ymlcore/presentation/src/commonMain/composeResources/files/whatsnew/19.jsoncore/presentation/src/commonMain/composeResources/files/whatsnew/ar/19.jsoncore/presentation/src/commonMain/composeResources/files/whatsnew/bn/19.jsoncore/presentation/src/commonMain/composeResources/files/whatsnew/es/19.jsoncore/presentation/src/commonMain/composeResources/files/whatsnew/fr/19.jsoncore/presentation/src/commonMain/composeResources/files/whatsnew/hi/19.jsoncore/presentation/src/commonMain/composeResources/files/whatsnew/it/19.jsoncore/presentation/src/commonMain/composeResources/files/whatsnew/ja/19.jsoncore/presentation/src/commonMain/composeResources/files/whatsnew/ko/19.jsoncore/presentation/src/commonMain/composeResources/files/whatsnew/pl/19.jsoncore/presentation/src/commonMain/composeResources/files/whatsnew/ru/19.jsoncore/presentation/src/commonMain/composeResources/files/whatsnew/tr/19.jsoncore/presentation/src/commonMain/composeResources/files/whatsnew/zh-CN/19.jsonpackaging/linux/github-store-launcher.sh
| if ! sudo apt-get install -y rpmrebuild rpm >/dev/null 2>&1; then | ||
| echo "WARNING: could not install rpmrebuild — rpm will keep the crashing native launcher (GH#563)" | ||
| exit 0 | ||
| fi | ||
|
|
||
| WRAPPER="$(pwd)/packaging/linux/github-store-launcher.sh" | ||
| OUTDIR="$(mktemp -d)" | ||
| for rpm in "${rpms[@]}"; do | ||
| echo "Patching: $rpm" | ||
| # Overwrite the native launcher in-place with the JVM-direct wrapper. | ||
| # Keeps the %files manifest path intact, swaps ELF -> script. | ||
| if rpmrebuild --change-files="cp '$WRAPPER' ./opt/github-store/bin/GitHub-Store; chmod 0755 ./opt/github-store/bin/GitHub-Store" \ | ||
| -p -n -d "$OUTDIR" "$rpm"; then | ||
| rebuilt="$(find "$OUTDIR" -name '*.rpm' -newer "$WRAPPER" | head -n1 || true)" | ||
| if [ -n "$rebuilt" ]; then | ||
| mv -f "$rebuilt" "$rpm" | ||
| echo "Patched successfully: $rpm" | ||
| else | ||
| echo "WARNING: rpmrebuild produced no output for $rpm — keeping original" | ||
| fi | ||
| else | ||
| echo "WARNING: rpmrebuild failed for $rpm — keeping original (still crashes per GH#563)" | ||
| fi |
There was a problem hiding this comment.
Do not publish the original RPM when patching fails.
The exit 0 / warning paths here still let the workflow upload and release an RPM with the crashing native launcher intact. Since this PR claims RPMs bypass that launcher, patch-tool install failure or rpmrebuild failure should stop the job instead of silently shipping a broken artifact.
Suggested fix
if ! sudo apt-get install -y rpmrebuild rpm >/dev/null 2>&1; then
- echo "WARNING: could not install rpmrebuild — rpm will keep the crashing native launcher (GH#563)"
- exit 0
+ echo "::error::could not install rpmrebuild; refusing to publish an unpatched rpm"
+ exit 1
fi
@@
else
- echo "WARNING: rpmrebuild failed for $rpm — keeping original (still crashes per GH#563)"
+ echo "::error::rpmrebuild failed for $rpm; refusing to publish an unpatched rpm"
+ exit 1
fi📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if ! sudo apt-get install -y rpmrebuild rpm >/dev/null 2>&1; then | |
| echo "WARNING: could not install rpmrebuild — rpm will keep the crashing native launcher (GH#563)" | |
| exit 0 | |
| fi | |
| WRAPPER="$(pwd)/packaging/linux/github-store-launcher.sh" | |
| OUTDIR="$(mktemp -d)" | |
| for rpm in "${rpms[@]}"; do | |
| echo "Patching: $rpm" | |
| # Overwrite the native launcher in-place with the JVM-direct wrapper. | |
| # Keeps the %files manifest path intact, swaps ELF -> script. | |
| if rpmrebuild --change-files="cp '$WRAPPER' ./opt/github-store/bin/GitHub-Store; chmod 0755 ./opt/github-store/bin/GitHub-Store" \ | |
| -p -n -d "$OUTDIR" "$rpm"; then | |
| rebuilt="$(find "$OUTDIR" -name '*.rpm' -newer "$WRAPPER" | head -n1 || true)" | |
| if [ -n "$rebuilt" ]; then | |
| mv -f "$rebuilt" "$rpm" | |
| echo "Patched successfully: $rpm" | |
| else | |
| echo "WARNING: rpmrebuild produced no output for $rpm — keeping original" | |
| fi | |
| else | |
| echo "WARNING: rpmrebuild failed for $rpm — keeping original (still crashes per GH#563)" | |
| fi | |
| if ! sudo apt-get install -y rpmrebuild rpm >/dev/null 2>&1; then | |
| echo "::error::could not install rpmrebuild; refusing to publish an unpatched rpm" | |
| exit 1 | |
| fi | |
| WRAPPER="$(pwd)/packaging/linux/github-store-launcher.sh" | |
| OUTDIR="$(mktemp -d)" | |
| for rpm in "${rpms[@]}"; do | |
| echo "Patching: $rpm" | |
| # Overwrite the native launcher in-place with the JVM-direct wrapper. | |
| # Keeps the %files manifest path intact, swaps ELF -> script. | |
| if rpmrebuild --change-files="cp '$WRAPPER' ./opt/github-store/bin/GitHub-Store; chmod 0755 ./opt/github-store/bin/GitHub-Store" \ | |
| -p -n -d "$OUTDIR" "$rpm"; then | |
| rebuilt="$(find "$OUTDIR" -name '*.rpm' -newer "$WRAPPER" | head -n1 || true)" | |
| if [ -n "$rebuilt" ]; then | |
| mv -f "$rebuilt" "$rpm" | |
| echo "Patched successfully: $rpm" | |
| else | |
| echo "WARNING: rpmrebuild produced no output for $rpm — keeping original" | |
| fi | |
| else | |
| echo "::error::rpmrebuild failed for $rpm; refusing to publish an unpatched rpm" | |
| exit 1 | |
| fi |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/build-desktop-platforms.yml around lines 412 - 434, The
workflow currently continues and may publish original RPMs when patching fails;
change the failure branches so the job exits non‑zero instead of continuing.
Specifically, replace the apt-get failure path that currently does "echo ...;
exit 0" with a non‑zero exit (e.g. exit 1) and similarly change the rpmrebuild
failure branches (the else blocks that echo warnings after rpmrebuild or when
"$rebuilt" is empty) to fail the job (exit 1) rather than logging and
continuing; ensure you also do not mv the original file when rpmrebuild produced
no output (the mv inside the success branch should remain the only place
originals are overwritten). Use the WRAPPER, OUTDIR, rpmrebuild invocation, and
the "$rebuilt" check to locate the exact places to modify.
Closes #563.
Root cause
The jpackage-generated native launcher (
bin/GitHub-Store) segfaults inside glibcsetenv()before the JVM is even mapped, on glibc >= 2.42 (Fedora 43, Ubuntu 26.04, Debian 13, Bazzite). The reporter's coredump pins it precisely:It is independent of environment contents (the reporter's
envdiff between clean/poisoned states was empty, and a minimalenv -ilaunch still crashed) and independent of distro / DE (reproduced on Fedora, Ubuntu, Debian, KDE, GNOME). The poison is in the bundled launcher'ssetenvpath against glibc 2.42's reworkedenvironhandling — nothing we can fix inside the launcher binary, which is generated by jpackage.Fix
Sidestep the broken native launcher entirely and start the bundled JVM directly — exactly what the Flatpak build already does in production (
java -jar), which is why Flatpak was never affected.New committed launcher
packaging/linux/github-store-launcher.sh:lib/runtime/bin/javarelative to its own path.lib/app/*.cfgfor classpath, main class, and JVM options (expanding$APPDIR/$ROOTDIR/$BINDIRtokens).execs the JVM directly. Works identically inside an AppImage mount,/opt/github-store(deb / rpm / Arch), or the raw Compose app-image dir.Wired into all four Linux artifacts in CI:
AppRunnow execs the wrapper instead of the native launcher.usr/binsymlink +.desktopExec repointed to the wrapper..desktopExec path unchanged, transparently runs the wrapper).rpmrebuildoverwrites the launcher in place; non-fatal if rpmrebuild is unavailable so the rest of the Linux build still ships.Validation
Simulated a jpackage layout locally (fake
.cfgwith multi-line classpath,$APPDIRtokens, multiplejava-options, plus a stubjava) and confirmed the wrapper resolves classpath + main class + options + passes through user args (deep links) correctly.bash -nclean, workflow YAML validated.Needs a
generate-installersCI run to validate the actual deb/rpm/AppImage/Arch repack against real jpackage output before merge — I can't produce signed Linux artifacts locally. cc reporter @nitinkmr333 for a confirmation build.What's-new bullet added to
19.json+ all 12 locales.Summary by CodeRabbit