Files
claude-desktop-debian/docs/testing/cases/platform-integration.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

14 KiB

Platform Integration

Tests covering autostart, Cowork integration, WebGL graceful degradation, .desktop-launch env inheritance, encrypted env-var storage, the macOS/Windows-only Computer Use feature, and Dispatch session pairing. See ../matrix.md for status.

T09 — AutoStart via XDG

Severity: Critical Surface: XDG Autostart Applies to: All rows Issues: PR #450

Steps:

  1. In Settings, toggle "Open at Login" / "Start at boot" ON.
  2. Inspect ~/.config/autostart/ for a .desktop entry.
  3. Logout/login. Verify app launches automatically.
  4. Toggle OFF. Verify the autostart entry is removed.

Expected: Toggling ON creates a ~/.config/autostart/*.desktop entry that is XDG-spec compliant (not a custom systemd unit or shell hook). After login, app launches automatically. Toggling OFF removes the entry.

Diagnostics on failure: ls -la ~/.config/autostart/, content of the .desktop file, desktop-file-validate on it, launcher log.

References: PR #450

Code anchors:

  • scripts/frame-fix-wrapper.js:376 — XDG Autostart shim intercepting app.{get,set}LoginItemSettings (writes/removes $XDG_CONFIG_HOME/autostart/claude-desktop.desktop).
  • scripts/frame-fix-wrapper.js:429buildAutostartContent() emits the spec-compliant [Desktop Entry] block.
  • build-reference/app-extracted/.vite/build/index.js:524205 — upstream isStartupOnLoginEnabled / setStartupOnLoginEnabled IPC surface that the wrapper interposes on.

T10 — Cowork integration

Severity: Should Surface: Cowork tab + VM daemon Applies to: All rows Issues: docs/learnings/cowork-vm-daemon.md

Steps:

  1. Sign into the app. Open the Cowork tab.
  2. Confirm Cowork-specific UI renders (ghost icon in topbar, Cowork menus).
  3. Trigger a Cowork action that needs the VM daemon.
  4. Kill the VM daemon process; verify it respawns within the documented timeout.

Expected: Cowork features render. VM daemon spawns when needed, files are visible, daemon respawns within the documented timeout if it crashes.

Diagnostics on failure: pgrep -af cowork, daemon logs, launcher log, the respawn-logic code path (see learnings doc).

References: docs/learnings/cowork-vm-daemon.md

Code anchors:

  • build-reference/app-extracted/.vite/build/index.js:143371 — upstream's Windows named-pipe path (\\.\pipe\cowork-vm-service) that scripts/patches/cowork.sh Patch 1 rewrites to $XDG_RUNTIME_DIR/cowork-vm-service.sock.
  • build-reference/app-extracted/.vite/build/index.js:143453kUe() retry loop (5 attempts, 1 s gap) that the auto-launch injection from Patch 6 piggybacks on after the rewrite.
  • scripts/patches/cowork.sh:244 — Patch 6 (auto-launch + stdio pipe + 10 s rate-limited respawn — issue #408).
  • scripts/patches/cowork.sh:365 — Patch 6b (extends the reinstall-delete list with sessiondata.img / rootfs.img.zst so a wedged daemon can self-recover).

T12 — WebGL warn-only

Severity: Could Surface: Chromium GPU diagnostics Applies to: All rows (especially VM rows and hybrid-GPU laptops) Issues:

Steps:

  1. Launch the app. Open DevTools → navigate to chrome://gpu.
  2. Inspect WebGL1/WebGL2 status.
  3. Use the app for ~5 minutes — exercise UI, sidebar, settings.

Expected: WebGL1/2 may report as blocklisted (typical on virtio-gpu in VMs and on hybrid GPU laptops). This is informational. UI continues to render without graphical glitches; no feature is broken by the blocklist.

Diagnostics on failure: chrome://gpu full content, screenshot of any visual glitch, glxinfo | head -20 (X11) or eglinfo (Wayland), lspci -k | grep -A2 VGA.

References:

Code anchors:

  • build-reference/app-extracted/.vite/build/index.js:524809app.disableHardwareAcceleration() is gated on the user-toggleable isHardwareAccelerationDisabled setting; upstream does not pass --ignore-gpu-blocklist or --use-gl=*, so chrome://gpu reflects Chromium's stock blocklist behaviour.
  • build-reference/app-extracted/.vite/build/index.js:500571 — the only webgl:!1 override is scoped to the feedback popup (in-memory-feedback partition); main UI does not disable WebGL.

S17 — App launched from .desktop inherits shell PATH

Severity: Critical Surface: .desktop-launch env handling Applies to: All rows Issues:

Steps:

  1. Configure ~/.bashrc (or ~/.zshrc) with export PATH="$HOME/.custom-bin:$PATH" and a custom binary in that dir.
  2. Launch the app via dmenu/krunner/GNOME Activities/Plasma launcher (i.e. not from a terminal).
  3. Open a Code-tab terminal pane. Run which <custom-binary>.
  4. Repeat for npm, node, git, gh.

Expected: Code session can find tools defined in the user's shell profile, even when the app was launched non-interactively. Either the launcher script sources the user's shell profile, or the app reads ~/.bashrc / ~/.zshrc to extract PATH the way macOS does.

Diagnostics on failure: echo $PATH from inside the integrated terminal, the env passed to the app process (cat /proc/$(pgrep -f electron)/environ | tr '\0' '\n' | grep PATH), launcher log.

References: Local sessions, Session not finding installed tools

Code anchors:

  • build-reference/app-extracted/.vite/build/index.js:259300SLr() resolves the bundled shell-path-worker/shellPathWorker.js.
  • build-reference/app-extracted/.vite/build/index.js:259349NLr() forks it via utilityProcess.fork; on success FX() (line 259311) merges the extracted env into process.env.
  • build-reference/app-extracted/.vite/build/shell-path-worker/shellPathWorker.js:205extractPathFromShell() runs the user's login shell (-l -i) and parses the printed $PATH between sentinels (mac-style env inheritance now applied on Linux too).

S18 — Local environment editor persists across reboot

Severity: Should Surface: Local env editor / encrypted store Applies to: All rows Issues:

Steps:

  1. Open the local environment editor. Add TEST_VAR=hello.
  2. Restart the app — verify variable is still there.
  3. Reboot the host. Sign back in. Verify variable is still there.

Expected: Variables saved via the local environment editor (per-app, encrypted) survive a logout/login cycle and a full reboot. On Linux this implies the encrypted store is wired to libsecret / kwallet / gnome-keyring and unlocks at session start.

Diagnostics on failure: secret-tool search (libsecret), kwallet5-query (KDE), seahorse UI inspection (GNOME), launcher log, the env-editor IPC call.

References: Local sessions

Code anchors:

  • build-reference/app-extracted/.vite/build/index.js:259251I2t = new K_({ name: "ccd-environment-config", ... }) electron-store backing file (~/.config/Claude/ccd-environment-config.json).
  • build-reference/app-extracted/.vite/build/index.js:259253hLr() writes via safeStorage.encryptString (libsecret on Linux).
  • build-reference/app-extracted/.vite/build/index.js:259268J1() decrypts on read; bails to {} if safeStorage reports encryption unavailable (no keyring backend running).
  • build-reference/app-extracted/.vite/build/index.js:70782LocalSessionEnvironment.save IPC entry that calls into hLr.

S22 — Computer-use toggle is absent or visibly disabled on Linux

Severity: Should Surface: Settings → Desktop app → General Applies to: All rows Issues:

Steps:

  1. Open Settings → Desktop app → General.
  2. Look for the "Computer use" toggle.

Expected: Toggle either does not render on Linux, or renders as a disabled control with a clear "not supported on Linux" hint. Must not appear functional and silently fail (e.g. flip on but never produce screen-control behavior).

Diagnostics on failure: Screenshot of the Settings page, DevTools inspection of the toggle DOM (is it conditionally hidden? disabled? always-rendered?), launcher log.

References: Let Claude use your computer, Dispatch and computer use

Code anchors:

  • build-reference/app-extracted/.vite/build/index.js:240557qDA = new Set(["darwin", "win32"]) excludes Linux from the computer-use platform set.
  • build-reference/app-extracted/.vite/build/index.js:241190TF() (the master enable check) short-circuits to false when qDA.has(process.platform) is false, so toggling chicagoEnabled on Linux can't activate the feature.
  • build-reference/app-extracted/.vite/build/index.js:242387tvr() returns { status: "unsupported", reason: "Computer use is not available on this platform", unsupportedCode: "unsupported_platform" } for the Settings UI — confirms the toggle should render with a platform-unavailable hint, not silent failure.

S23 — Dispatch-spawned sessions don't soft-lock on a never-approvable computer-use prompt

Severity: Critical (for Dispatch users) Surface: Dispatch session lifecycle on Linux Applies to: All rows with Dispatch enabled Issues:

Steps:

  1. From a paired phone, dispatch a task that would invoke computer use.
  2. Observe the Code-tab session that spawns on the desktop.
  3. Try to interact with other parts of the app.

Expected: Permission prompt times out or denies cleanly rather than hanging the session indefinitely. User can continue interacting with the rest of the app.

Diagnostics on failure: Screenshot of session state, launcher log, sidebar state (is the Dispatch session blocking the whole sidebar?), pgrep -af claude.

References: Sessions from Dispatch

Code anchors:

  • build-reference/app-extracted/.vite/build/index.js:512789tool_permission_request notification handler explicitly skips toolName.startsWith("computer:"), so the desktop never queues a user-facing prompt for computer-use tool calls (which couldn't run on Linux anyway — see S22).
  • build-reference/app-extracted/.vite/build/index.js:241190TF() gates computer-use execution off entirely on Linux, so a Dispatch-spawned session that requests it should hit the upstream "Set up computer use" remote-client setup card (index.js:330114) rather than block on a desktop prompt.

S24 — Dispatch-spawned Code session appears with badge and notification

Severity: Critical Surface: Dispatch handoff Applies to: All rows with Dispatch enabled Issues:

Steps:

  1. From a paired phone, dispatch a task that routes to Code (e.g. "fix this bug").
  2. Observe the desktop sidebar.
  3. Confirm a desktop notification fires.
  4. Open the session and confirm 30-min approval expiry per upstream docs.

Expected: Dispatch task creates a sidebar entry tagged Dispatch, posts a desktop notification, and lands ready for review. App-permission approvals on this session expire after 30 minutes per upstream docs.

Diagnostics on failure: Screenshot of sidebar (badge present?), notification daemon state, launcher log, the Dispatch pairing config under ~/.config/Claude/.

References: Sessions from Dispatch, Dispatch and computer use

Code anchors:

  • build-reference/app-extracted/.vite/build/index.js:144561Sd = "dispatch_child" session-type constant.
  • build-reference/app-extracted/.vite/build/index.js:512200onRemoteSessionStart IPC routes a Dispatch-initiated child session into the local sidebar via dispatchOnRemoteSessionStart.
  • build-reference/app-extracted/.vite/build/index.js:285621notifyDispatchParentIfNeeded() posts the Task "<title>" <state> meta-notification when the dispatch child finishes (lands the result in the parent thread's notification queue).
  • build-reference/app-extracted/.vite/build/index.js:285954kind:"dispatch_child" is the sidebar badge tag.

S25 — Mobile pairing survives Linux session restart

Severity: Should Surface: Dispatch pairing persistence Applies to: All rows with Dispatch enabled Issues:

Steps:

  1. Pair the desktop with a phone.
  2. Quit the app fully. Re-launch.
  3. Try a Dispatch task. Verify pairing still works without re-pairing.
  4. Logout/login the desktop. Re-test.

Expected: Pairing remains active across app restart and logout/login. Pairing token is stored under ~/.config/Claude/ (or wherever the secure store lives) and survives.

Diagnostics on failure: ls -la ~/.config/Claude/, secret-store inspection, launcher log, pairing-flow IPC.

References: Sessions from Dispatch

Code anchors:

  • build-reference/app-extracted/.vite/build/index.js:511984ZEe = "coworkTrustedDeviceToken" electron-store key for the trusted-device token.
  • build-reference/app-extracted/.vite/build/index.js:511989oYn() writes the token via safeStorage.encryptString (libsecret on Linux); aYn() (:512003) decrypts on read.
  • build-reference/app-extracted/.vite/build/index.js:512022gYn() re-enrolls via POST /api/auth/trusted_devices only when there's no cached token, so a successful pair survives restart.
  • build-reference/app-extracted/.vite/build/index.js:330229_5r = "bridge-state.json" (per-org/account bridge state under ~/.config/Claude/bridge-state.json); JF()/X0A() at :330230 read/locate it.