Skip to content

fix: Linux launch crash on glibc 2.42+ (bypass jpackage native launcher)#685

Open
rainxchzed wants to merge 1 commit into
mainfrom
fix/563-linux-launcher-setenv-segv
Open

fix: Linux launch crash on glibc 2.42+ (bypass jpackage native launcher)#685
rainxchzed wants to merge 1 commit into
mainfrom
fix/563-linux-launcher-setenv-segv

Conversation

@rainxchzed
Copy link
Copy Markdown
Member

@rainxchzed rainxchzed commented May 28, 2026

Closes #563.

Root cause

The jpackage-generated native launcher (bin/GitHub-Store) segfaults inside glibc setenv() 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:

#0  setenv () from /lib64/libc.so.6        <- SEGV at fffffffffffe03a0
#1  jvmLauncherStartJvm ()
#2  main ()

It is independent of environment contents (the reporter's env diff between clean/poisoned states was empty, and a minimal env -i launch still crashed) and independent of distro / DE (reproduced on Fedora, Ubuntu, Debian, KDE, GNOME). The poison is in the bundled launcher's setenv path against glibc 2.42's reworked environ handling — 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:

  • Locates the bundled lib/runtime/bin/java relative to its own path.
  • Parses the jpackage lib/app/*.cfg for classpath, main class, and JVM options (expanding $APPDIR / $ROOTDIR / $BINDIR tokens).
  • 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:

  • AppImageAppRun now execs the wrapper instead of the native launcher.
  • Archusr/bin symlink + .desktop Exec repointed to the wrapper.
  • deb — repack step swaps the native ELF for the wrapper in place (.desktop Exec path unchanged, transparently runs the wrapper).
  • rpmrpmrebuild overwrites 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 .cfg with multi-line classpath, $APPDIR tokens, multiple java-options, plus a stub java) and confirmed the wrapper resolves classpath + main class + options + passes through user args (deep links) correctly. bash -n clean, workflow YAML validated.

Needs a generate-installers CI 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

  • Bug Fixes
    • Fixed Linux desktop app crashing on startup for systems with glibc 2.42+ (including Fedora 43, Ubuntu 26.04, Debian 13, and Bazzite). The app now launches reliably across AppImage, Debian, RPM, and Arch Linux packages.

Review Change Stack

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 28, 2026

Walkthrough

Linux 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.

Changes

Linux launcher wrapper and packaging integration

Layer / File(s) Summary
JVM-direct launcher script
packaging/linux/github-store-launcher.sh
Bash script computes app root from its location, verifies bundled JVM exists, locates and parses the jpackage .cfg config file to extract classpath/modulepath and main class/module, expands $APPDIR/$ROOTDIR/$BINDIR tokens, then execs java with the resolved launch arguments. Exits with error if JVM, config, or launch target cannot be resolved.
AppImage/AppDir packaging wiring
.github/workflows/build-desktop-platforms.yml (lines 325–335)
Copies launcher wrapper into AppDir, makes it executable, and updates AppRun entrypoint to exec the wrapper script instead of the native GitHub-Store binary.
Debian package launcher replacement
.github/workflows/build-desktop-platforms.yml (lines 368–403)
Step defines WRAPPER variable, then for each built .deb, moves the native GitHub-Store ELF to .bin, copies the wrapper to the original path, makes it executable, and warns if launcher is not found.
RPM package launcher replacement
.github/workflows/build-desktop-platforms.yml (lines 405–439)
Uses nullglob and conditionally installs rpmrebuild, then calls rpmrebuild --change-files to overwrite the in-package launcher with the wrapper script; warns and preserves original if tooling fails.
Arch Linux package launcher integration
.github/workflows/build-desktop-platforms.yml (lines 497–512)
Installs wrapper into opt/github-store/bin/, updates github-store.desktop Exec to wrapper path, and points usr/bin/github-store symlink to the wrapper instead of native binary.
Release notes and i18n translations
core/presentation/src/commonMain/composeResources/files/whatsnew/*/19.json (13 locales)
Documents that Linux desktop no longer crashes on glibc 2.42+ by announcing all Linux package formats now bypass the broken bundled launcher and start the JVM directly. Existing bullet about responsive desktop content width is retained in all locales.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • OpenHub-Store/GitHub-Store#281: Both PRs modify .github/workflows/build-desktop-platforms.yml's AppImage/AppDir packaging flow, where the main PR updates AppRun to exec the new github-store-launcher.sh wrapper.
  • OpenHub-Store/GitHub-Store#362: Both PRs modify the Arch Linux packaging path in the workflow, with the main PR wiring the new launcher while the retrieved PR scaffolds the initial launcher packaging logic.
  • OpenHub-Store/GitHub-Store#679: Both PRs update version code 19 release notes—the main PR adds the Linux launcher/crash fix content while the retrieved PR registers version 19 in KnownWhatsNewVersionCodes.ALL.

🐰 A launcher born from config and JVM dreams,
No more segfaults on systems with glibc schemes,
AppImage, deb, rpm, and Arch all stand,
Running Java direct across the Linux land!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: fixing Linux launch crashes on glibc 2.42+ by bypassing the jpackage native launcher, which is the core objective of this pull request.
Linked Issues check ✅ Passed The pull request comprehensively addresses the linked issue #563 by implementing a shell wrapper that bypasses the problematic native launcher to execute the JVM directly, resolving the segmentation fault on glibc 2.42+ systems with the specific podman quadlet environment.
Out of Scope Changes check ✅ Passed All changes are in scope: workflow modifications for distribution-specific launcher replacement, a new shell wrapper script for JVM execution, and release notes updates across multiple languages documenting the fix.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/563-linux-launcher-setenv-segv

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 28, 2026

Greptile Summary

Sidesteps the glibc 2.42+ crash in the jpackage native launcher by introducing a pure-bash wrapper (packaging/linux/github-store-launcher.sh) that reads the jpackage .cfg directly and execs the bundled JVM — mirroring the existing Flatpak approach. The wrapper is wired into all four Linux artifact types (AppImage, deb, rpm, Arch) in CI.

  • The new launcher script handles .cfg parsing for both classpath-mode and module-path-mode apps and expands jpackage tokens ($APPDIR, $ROOTDIR, $BINDIR) before building the exec invocation.
  • The deb repack step swaps the native ELF for the script in-place so the installed .desktop Exec path is unchanged; the rpm step uses rpmrebuild and is intentionally non-fatal if rpmrebuild is unavailable.
  • Localized what's-new entries are added across all 13 supported locales.

Confidence Score: 3/5

Safe 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 find | head -n1 pipeline in the launcher script exits silently when the AppDir contains more than one .cfg file — head terminates early, find receives SIGPIPE and exits non-zero, pipefail propagates it, and set -e kills the script with no diagnostic output. The deb patch step guards against the same pattern with || true, but the launcher itself does not. Fixing that one line would bring confidence significantly higher.

packaging/linux/github-store-launcher.sh — specifically line 31 and the module-path exec branch at lines 84–87.

Important Files Changed

Filename Overview
packaging/linux/github-store-launcher.sh New JVM-direct launcher that parses jpackage cfg and execs the bundled Java. `find
.github/workflows/build-desktop-platforms.yml Wires the new wrapper into AppImage, deb, rpm, and Arch build steps. deb step correctly uses `
core/presentation/src/commonMain/composeResources/files/whatsnew/19.json Adds what's-new bullet describing the Linux crash fix; no logic changes.

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]
Loading

Fix All in Claude Code

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)"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 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.

Suggested change
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)"

Fix in Claude Code

Comment on lines +84 to +87
if [ -n "$mainmodule" ]; then
exec "$JAVA" "${javaopts[@]}" \
${modulepath:+--module-path "$modulepath"} \
-m "$mainmodule" "$@"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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.

Fix in Claude Code


if [ -n "$mainmodule" ]; then
exec "$JAVA" "${javaopts[@]}" \
${modulepath:+--module-path "$modulepath"} \
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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.

Fix in Claude Code

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 1636530 and e6cfadf.

📒 Files selected for processing (15)
  • .github/workflows/build-desktop-platforms.yml
  • core/presentation/src/commonMain/composeResources/files/whatsnew/19.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/ar/19.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/bn/19.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/es/19.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/fr/19.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/hi/19.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/it/19.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/ja/19.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/ko/19.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/pl/19.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/ru/19.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/tr/19.json
  • core/presentation/src/commonMain/composeResources/files/whatsnew/zh-CN/19.json
  • packaging/linux/github-store-launcher.sh

Comment on lines +412 to +434
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
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

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.

Suggested change
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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Linux- segmentation fault if multiple podman quadlets start at boot

1 participant