Files
claude-desktop-debian/docs/testing/cases/distribution.md
Aaddrick 3506c14918 test(harness): add Linux compatibility test harness (#579)
Build out a Playwright-based regression-detection harness covering
the compat-matrix surfaces (KDE-W, KDE-X, GNOME, Sway, i3, Niri,
packaging formats). Adds:

- Planning + decision docs under docs/testing/ — README, matrix,
  runbook, automation, cases/ (11 case files), quick-entry-closeout
- Playwright scaffolding (config, tsconfig)
- 78 spec runners under tools/test-harness/src/runners/ — T## case-
  doc runners and S## distribution/smoke runners
- Substrate primitives in tools/test-harness/src/lib/: AX-tree
  loader (snapshotAx + waitForAxNode + axTreeToSnapshot), focus-
  shifter, eipc-registry, niri-native bridge, drag-drop bridge,
  electron-mocks, claudeai page-objects, inspector client

S03 (DEB Depends declared) and S04 (RPM Requires declared) ship
marked test.fail() — they're regression detectors for the case-doc
gap (deb.sh emits no Depends:, rpm.sh sets AutoReqProv: no), and
the expected-failure shape lets them report green on every host
until upstream packaging starts declaring runtime deps.

127 files, no runtime changes; harness is opt-in via
'cd tools/test-harness && npx playwright test'.

Co-authored-by: Claude <claude@anthropic.com>
2026-05-04 23:17:37 -04:00

12 KiB

Distribution — DEB, RPM, AppImage

Tests covering Ubuntu/DEB-specific install behavior, Fedora/RPM-specific install behavior, AppImage fallback paths, and the auto-update interaction with system package managers. See ../matrix.md for status.

S01 — AppImage launches without manual libfuse2t64 install

Severity: Critical (for Ubuntu users) Surface: AppImage runtime / FUSE Applies to: Ubu (and any Ubuntu 24.04+ host) Issues:

Steps:

  1. Fresh Ubuntu 24.04 install with default packages only.
  2. Download the project AppImage.
  3. Make executable and run it.

Expected: AppImage runs without first installing libfuse2t64. Either the AppImage bundles its own FUSE shim, the .desktop/postinst declares the dep, or the launcher gives a clear error pointing at the package name.

Currently: Fails on Ubuntu 24.04 with dlopen(): error loading libfuse.so.2. Workaround: sudo apt install libfuse2t64. Not yet filed.

Diagnostics on failure: Full stderr from the AppImage launch, ldd ./claude-desktop-*.AppImage, dpkg -l | grep -i fuse.

References:

Code anchors: scripts/packaging/appimage.sh:226 (downloads the upstream appimagetool AppImage as-is — no FUSE shim or static-mksquashfs bundling), scripts/launcher-common.sh:64 (AppImage forces --no-sandbox "due to FUSE constraints"), .github/workflows/test-artifacts.yml:47 (CI installs libfuse2 before running the AppImage — i.e. the runtime hard-depends on libfuse2/libfuse2t64). No postinst dep declaration or user-facing FUSE error message exists.

S02 — XDG_CURRENT_DESKTOP=ubuntu:GNOME doesn't break DE detection

Severity: Critical Surface: DE detection / patch gate Applies to: Ubu Issues:

Steps:

  1. On Ubuntu 24.04 (where XDG_CURRENT_DESKTOP=ubuntu:GNOME), launch the app.
  2. Inspect launcher log for any DE-detection branches that should fire as GNOME.
  3. Audit scripts/launcher-common.sh and any DE-gated patches for string-equality checks against XDG_CURRENT_DESKTOP.

Expected: DE-detection logic handles Ubuntu's colon-separated value. contains "GNOME" or splitting on : is the safe pattern; == "GNOME" would miss Ubuntu.

Diagnostics on failure: echo $XDG_CURRENT_DESKTOP, the relevant launcher.sh code path, launcher log, the patches that ran or didn't.

References: Surfaced via session-capture review.

Code anchors: scripts/launcher-common.sh:35-44 (Niri auto-detect lowercases XDG_CURRENT_DESKTOP and uses *niri* glob — handles colon-separated values), scripts/patches/quick-window.sh:34-35 and :117-118 (KDE gate uses .toLowerCase().includes("kde") — substring, not equality), scripts/doctor.sh:304 (purely informational _info "Desktop: $desktop", no branching). No == equality checks against XDG_CURRENT_DESKTOP exist anywhere in shell or patched JS.

S03 — DEB install via APT pulls all required runtime deps

Severity: Critical Surface: APT repository / dependency declarations Applies to: Ubu (any DEB-based distro) Issues: docs/learnings/apt-worker-architecture.md

Steps:

  1. Add the project's APT repo per the README install instructions.
  2. sudo apt install claude-desktop on a fresh container/VM.
  3. Run claude-desktop — first launch should succeed with no further package installs.

Expected: All transitive runtime deps are declared in the package and pulled by APT. First launch succeeds without manual apt install of any extra package.

Diagnostics on failure: apt-cache depends claude-desktop, missing-library errors from the launcher, ldd against the binary.

References: docs/learnings/apt-worker-architecture.md

Code anchors: scripts/packaging/deb.sh:185-197 (DEBIAN/control file — no Depends: field is emitted; relies on bundled Electron + the comment "No external dependencies are required at runtime" at line 183), scripts/packaging/deb.sh:202-230 (postinst only sets chrome-sandbox suid, no dep-pull). Worker chain serving the package: worker/src/worker.js:22-31 (DEB_RE) and :33-43 (302 → GitHub Releases).

S04 — RPM install via DNF pulls all required runtime deps

Severity: Critical Surface: DNF repository / dependency declarations Applies to: KDE-W, KDE-X, GNOME, Sway, i3, Niri (any RPM-based distro) Issues: docs/learnings/apt-worker-architecture.md (covers both APT and DNF)

Steps:

  1. Add the project's DNF repo per the README.
  2. sudo dnf install claude-desktop on a fresh container/VM.
  3. Run claude-desktop — first launch should succeed.

Expected: All transitive runtime deps are declared in the RPM and pulled by DNF. First launch succeeds with no further package installs.

Diagnostics on failure: dnf repoquery --requires claude-desktop, rpm -qR claude-desktop, launcher missing-library errors.

References: docs/learnings/apt-worker-architecture.md

Code anchors: scripts/packaging/rpm.sh:188 (AutoReqProv: no — explicitly disables RPM's auto-dep generation; spec declares no Requires:), scripts/packaging/rpm.sh:194-198 (strip + build-id disabled because Electron binaries don't tolerate them — bundled approach). Worker chain: worker/src/worker.js:28-31 (RPM_RE).

S05 — Doctor recognises dnf-installed package, doesn't false-flag as AppImage

Severity: Should Surface: Doctor package-format detection Applies to: KDE-W, KDE-X, GNOME, Sway, i3, Niri Issues:

Steps:

  1. On a Fedora/Nobara/RPM-based distro with claude-desktop installed via dnf, run claude-desktop --doctor.
  2. Look for the install-method line.

Expected: Doctor detects rpm install (e.g. via rpm -qf against the binary path) and reports it cleanly. No not found via dpkg (AppImage?) warning.

Currently: Doctor's install-method check is gated on command -v dpkg-query, so on RPM-only hosts (no dpkg installed) the block is skipped entirely — no install-method line is printed. On hosts that have both dpkg-query and an rpm-installed claude-desktop (uncommon, e.g. mixed Debian + dnf), the misleading claude-desktop not found via dpkg (AppImage?) WARN does fire. Either way, no rpm -qf branch exists. Affects KDE-W, KDE-X, GNOME, Sway, i3, Niri rows (T13). Not yet filed.

Diagnostics on failure: Full --doctor output, rpm -qf $(which claude-desktop), the doctor source line that decides the format.

References: T13

Code anchors: scripts/doctor.sh:353-362 — install-method check is gated on command -v dpkg-query; only runs on Debian-family hosts. Falls through to _warn 'claude-desktop not found via dpkg (AppImage?)' only if dpkg-query is present but returns empty. On Fedora/RPM hosts (dpkg-query absent), the entire block is skipped and no install-method line is printed at all — neither the misleading WARN nor a correct rpm -qf PASS. The drift is "no detection" rather than "false-flag as AppImage" on dpkg-less systems.

S15 — AppImage extraction (--appimage-extract) works as documented fallback

Severity: Could Surface: AppImage runtime / FUSE-less fallback Applies to: Any AppImage row Issues:

Steps:

  1. On a host without FUSE, run ./claude-desktop-*.AppImage --appimage-extract.
  2. Inspect squashfs-root/.
  3. Run squashfs-root/AppRun.

Expected: Extraction completes. squashfs-root/AppRun launches the app cleanly without FUSE.

Diagnostics on failure: Extraction stderr, ls squashfs-root/, AppRun stderr.

References: Linked from the runtime error message when FUSE is missing.

Code anchors: scripts/packaging/appimage.sh:282 and :312 (built with stock appimagetool, which always supports --appimage-extract), scripts/packaging/appimage.sh:70-118 (AppRun script that lives at squashfs-root/AppRun after extraction). CI exercises this path: tests/test-artifact-appimage.sh:36-44 and .github/workflows/ci.yml:388 both run --appimage-extract and assert squashfs-root/ exists.

S16 — AppImage mount cleans up on app exit

Severity: Should Surface: AppImage mount lifecycle Applies to: Any AppImage row Issues: CLAUDE.md "Common Gotchas"

Steps:

  1. Launch the AppImage. Confirm mount | grep claude shows the mount.
  2. Quit the app cleanly via tray → Quit (or Ctrl+Q).
  3. Re-run mount | grep claude — mount should be gone.

Expected: AppImage's mount at /tmp/.mount_claude* is unmounted and the directory removed when all child Electron processes exit. Stale mounts after force-quit are handled by pkill -9 -f "mount_claude" per CLAUDE.md but should not be the common case.

Diagnostics on failure: mount | grep claude after exit, ls -la /tmp/.mount_claude*, pgrep -af claude, journalctl -k -n 50 for mount errors.

References: CLAUDE.md "Common Gotchas"

Code anchors: Mount lifecycle is owned by upstream appimagetool's runtime, not this repo — scripts/packaging/appimage.sh:282/:312 invokes the stock tool with no custom AppRun-side cleanup. CLAUDE.md:179-183 documents pkill -9 -f "mount_claude" as the manual recovery for stale mounts after force-quit. No project-side unmount handler exists; the test asserts upstream behavior, not ours.

S26 — Auto-update is disabled when installed via apt / dnf

⚠ Missing in build 1.5354.0 — No project-side suppression of upstream auto-update exists; the launcher exports ELECTRON_FORCE_IS_PACKAGED=true, which causes upstream's lii() gate to return true on Linux and the auto-update tick loop to start. Suppression is "accidental" — it relies on Electron's built-in autoUpdater module being unimplemented on Linux (so setFeedURL/checkForUpdates throw, the error listener logs, and no download happens). Tracked at #567; re-verify after next upstream bump.

Severity: Critical Surface: Auto-update path Applies to: All DEB/RPM rows Issues: #567

Steps:

  1. Install via APT or DNF.
  2. Launch the app and let it sit for ~5 minutes.
  3. Inspect launcher log + filesystem for any auto-update download attempt.

Expected: When installed via the project's APT or DNF repo, the in-app auto-update path is suppressed. The app does not download replacement binaries (which would race the package manager). Updates flow through apt upgrade / dnf upgrade only. AppImage installs may continue to self-update or punt to the user.

Diagnostics on failure: Launcher log, network captures (look for downloads from releases.anthropic.com or api.anthropic.com/api/desktop/linux/...), filesystem changes under ~/.config/Claude/.

References: docs/learnings/apt-worker-architecture.md

Code anchors: scripts/launcher-common.sh:249 (export ELECTRON_FORCE_IS_PACKAGED=true — makes upstream think it's installed); build-reference/app-extracted/.vite/build/index.js:508761-508769 (upstream lii() returns hA.app.isPackaged on Linux — passes the gate); :508554-508559 (only suppression hook is enterprise-policy disableAutoUpdates, no Linux/distro carve-out); :508770-508774 (feed URL https://api.anthropic.com/api/desktop/linux/<arch>/squirrel/update?...); :508800-508803 (calls hA.autoUpdater.setFeedURL + .checkForUpdates() unconditionally on Linux). No patch in scripts/patches/*.sh neutralizes the autoUpdater module or sets disableAutoUpdates. AppImage continues to ship update info: scripts/packaging/appimage.sh:308-309 (gh-releases-zsync zsync metadata embedded for releases).