Files
claude-desktop-debian/README.md

292 lines
17 KiB
Markdown
Raw Normal View History

# Claude Desktop for Linux
This project provides build scripts to run Claude Desktop natively on Linux systems. It repackages the official Windows application for Linux distributions, producing `.deb` packages (Debian/Ubuntu), `.rpm` packages (Fedora/RHEL), distribution-agnostic AppImages, an [AUR package](https://aur.archlinux.org/packages/claude-desktop-appimage) for Arch Linux, and a Nix flake for NixOS.
**Note:** This is an unofficial build script. For official support, please visit [Anthropic's website](https://www.anthropic.com). For issues with the build script or Linux implementation, please [open an issue](https://github.com/aaddrick/claude-desktop-debian/issues) in this repository.
---
> **⚠️ APT migration notice (April 2026)**
feat: KVM/bwrap isolation backends for cowork mode (#269) * feat: add KVM/bwrap isolation backends for cowork mode Refactor cowork-vm-service.js from monolithic VMManager into a pluggable backend architecture with three isolation levels: - HostBackend: direct execution on host (existing Phase 1 behavior) - BwrapBackend: bubblewrap namespace sandbox (PID/mount isolation) - KvmBackend: full QEMU/KVM VM with vsock, virtiofs, QMP monitor Backend auto-detected at startup (KVM > bwrap > host) or overridden via COWORK_VM_BACKEND env var. Shared helpers extracted for env filtering, arg cleanup, command resolution, and work dir handling. Also: - Add Cowork Mode section to --doctor diagnostics with per-tool checks and distro-specific install hints - Update Patch 4 to extract real win32 file entries for linux bundle manifest (enables app download infrastructure) - Update handover documentation for Phase 2/3 architecture Co-Authored-By: Claude <claude@anthropic.com> * fix: correct KvmBackend vsock port, direction, and kernel cmdline The guest sdk-daemon connects TO the host (CID=2), not the other way around. Confirmed via disassembly of the guest binary: the vsock port is 51234 (0xC822), matching the Hyper-V GUID in the Windows service. - Change VSOCK_GUEST_PORT from 2222 to 51234 - Reverse socat bridge: VSOCK-LISTEN (host listens) instead of VSOCK-CONNECT (host connecting to guest) - Add bridge server to accept persistent guest connection and route events/responses via callback map - Fix kernel cmdline: root=LABEL=cloudimg-rootfs (matches fstab) Co-Authored-By: Claude <claude@anthropic.com> * fix: patch VM download to use disk-backed temp dir on Linux Linux systems often mount /tmp as a small tmpfs (3-4GB). The VM rootfs download decompresses to ~9GB, causing ENOSPC. Patch the app's mkdtemp("wvm-") call to use the bundle directory (on real disk) instead of os.tmpdir() on Linux. Uses regex-based dynamic variable extraction to remain version-agnostic across minified code changes. Co-Authored-By: Claude <claude@anthropic.com> * fix: handle stale cowork socket (ECONNREFUSED) on Linux Stale sockets from previous sessions give ECONNREFUSED instead of ENOENT, bypassing the retry loop and auto-launch entirely. Fix: - Expand ENOENT check in retry loop to include ECONNREFUSED on Linux - Add cleanup_stale_cowork_socket() to launcher scripts (all formats) that removes dead sockets before Electron starts - Increase tmpdir patch search window from 1000 to 2000 chars Co-Authored-By: Claude <claude@anthropic.com> * fix: preserve DNS resolution inside bwrap sandbox On systems using systemd-resolved, /etc/resolv.conf is a symlink to /run/systemd/resolve/stub-resolv.conf. The bwrap --tmpfs /run option wiped this out, breaking DNS resolution inside the sandbox and preventing the spawned Claude process from reaching the API. Bind-mount the resolved /run/systemd/resolve/ directory back into the sandbox as read-only to restore DNS. Co-Authored-By: Claude <claude@anthropic.com> * fix: harden cowork isolation and build patches - Remove broken _setupEventForwarding (events already flow through _handleGuestData); the second bridge connection was silently ignored - Mount $HOME read-only in bwrap sandbox; only workDir and explicit mounts are writable (prevents writes to ~/.ssh, ~/.gnupg, etc.) - Scope Patch 4 win32 extraction to actual win32:{} block via brace counting to avoid crossing into darwin/linux sections - Set _qmpAvailable flag on QMP timeout instead of silently continuing - Wrap CID allocation at 65535 to prevent unbounded growth - Use execFileSync instead of execSync('which ...') in detectBackend - Coerce response ID to String for Map lookup in _handleGuestData - Use non-greedy [\s\S]*? in Patch 6 regex for nested brace robustness - Update patch count threshold from 4 to 5 after adding Patch 8 - Add age-based fallback for stale socket cleanup when socat is missing - Use indexOf-based splice in Patch 8 instead of string.replace() - Extract shared resolveSdkBinary helper to deduplicate SDK resolution - Remove dead retryFuncRe/retryFuncMatch variables from Patch 6 Co-Authored-By: Claude <claude@anthropic.com> * fix: address security and correctness issues from code review - Replace execSync string interpolation with execFileSync for qemu-img calls to eliminate shell injection risk - Add path validation to readFile in both LocalBackend and KvmBackend to restrict reads to within the user's home directory - Fix QMP _sendQmpCommand timer leak by clearing timeout on success - Fix _pendingCallbacks.delete() to use String(msg.id) matching the String(msg.id) used in the .get() lookup - Extract FORWARDED_EVENTS constant, cleanup helper, extractBlock helper, and consolidate doctor tool checks (from simplifier pass) Co-Authored-By: Claude <claude@anthropic.com> * docs: update README cowork notice with isolation backends and doctor info Co-Authored-By: Claude <claude@anthropic.com> --------- Co-authored-by: Claude <claude@anthropic.com>
2026-02-28 21:13:09 -05:00
>
> The APT/DNF repo moved to `pkg.claude-desktop-debian.dev` (#493) — binaries are now served from GitHub Releases via a Cloudflare Worker so they don't hit the 100 MB per-file push cap on `gh-pages`. **DNF users are unaffected.** APT users on the legacy `aaddrick.github.io` sources.list will see a scheme-downgrade error on `apt update`. [One-line `sed` fix](#migrating-from-the-old-aaddrickgithubio-url).
---
## Features
- **Native Linux Support**: Run Claude Desktop without virtualization or Wine
- **MCP Support**: Full Model Context Protocol integration
Configuration file location: `~/.config/Claude/claude_desktop_config.json`
- **System Integration**:
- Global hotkey support (Ctrl+Alt+Space) - works on X11 and Wayland (via XWayland)
- System tray integration
- Desktop environment integration
2025-04-03 01:27:06 -04:00
### Screenshots
<p align="center">
<img src="https://raw.githubusercontent.com/aaddrick/claude-desktop-debian/main/docs/images/claude-desktop-screenshot1.png" alt="Claude Desktop running on Linux" />
</p>
<p align="center">
<img src="https://raw.githubusercontent.com/aaddrick/claude-desktop-debian/main/docs/images/claude-desktop-screenshot2.png" alt="Global hotkey popup" />
</p>
## Installation
2025-01-17 11:17:57 +01:00
### Using APT Repository (Debian/Ubuntu - Recommended)
Add the repository for automatic updates via `apt`:
```bash
# Add the GPG key
2026-04-23 16:12:05 -04:00
curl -fsSL https://pkg.claude-desktop-debian.dev/KEY.gpg | sudo gpg --dearmor -o /usr/share/keyrings/claude-desktop.gpg
# Add the repository
2026-04-23 16:12:05 -04:00
echo "deb [signed-by=/usr/share/keyrings/claude-desktop.gpg arch=amd64,arm64] https://pkg.claude-desktop-debian.dev stable main" | sudo tee /etc/apt/sources.list.d/claude-desktop.list
# Update and install
sudo apt update
sudo apt install claude-desktop
```
Future updates will be installed automatically with your regular system updates (`sudo apt upgrade`).
### Using DNF Repository (Fedora/RHEL - Recommended)
Add the repository for automatic updates via `dnf`:
```bash
# Add the repository
2026-04-23 16:12:05 -04:00
sudo curl -fsSL https://pkg.claude-desktop-debian.dev/rpm/claude-desktop.repo -o /etc/yum.repos.d/claude-desktop.repo
# Install
sudo dnf install claude-desktop
```
Future updates will be installed automatically with your regular system updates (`sudo dnf upgrade`).
2026-04-23 16:12:05 -04:00
#### Migrating from the old `aaddrick.github.io` URL
If you installed claude-desktop before April 2026, your repo config points at `https://aaddrick.github.io/claude-desktop-debian`. That URL now auto-redirects to `pkg.claude-desktop-debian.dev` — DNF follows the redirect transparently, but **apt refuses it as a security downgrade**, so `apt update` fails. Update your sources list to the new URL:
```bash
# APT (Debian/Ubuntu)
sudo sed -i 's|https://aaddrick\.github\.io/claude-desktop-debian|https://pkg.claude-desktop-debian.dev|g' \
/etc/apt/sources.list.d/claude-desktop.list
sudo apt update
# DNF (Fedora/RHEL) — optional refresh; the old URL still works but pointing directly at the new host is cleaner
sudo curl -fsSL https://pkg.claude-desktop-debian.dev/rpm/claude-desktop.repo \
-o /etc/yum.repos.d/claude-desktop.repo
```
Background: binaries for recent releases are no longer committed to the `gh-pages` branch — `.deb` files grew past GitHub's 100 MB per-file cap (#493). The new URL is fronted by a small Cloudflare Worker that serves the existing metadata directly and 302-redirects package downloads to the corresponding GitHub Release asset. Bandwidth and package bytes still come from GitHub; the Worker just handles the routing.
### Using AUR (Arch Linux)
The [`claude-desktop-appimage`](https://aur.archlinux.org/packages/claude-desktop-appimage) package is available on the AUR and is automatically updated with each release.
```bash
# Using yay
yay -S claude-desktop-appimage
# Or using paru
paru -S claude-desktop-appimage
```
The AUR package installs the AppImage build of Claude Desktop.
### Using Nix Flake (NixOS)
Install directly from the flake:
```bash
# Basic install
nix profile install github:aaddrick/claude-desktop-debian
# With MCP server support (FHS environment)
nix profile install github:aaddrick/claude-desktop-debian#claude-desktop-fhs
```
Or add to your NixOS configuration:
```nix
# flake.nix
{
inputs.claude-desktop.url = "github:aaddrick/claude-desktop-debian";
outputs = { nixpkgs, claude-desktop, ... }: {
nixosConfigurations.myhost = nixpkgs.lib.nixosSystem {
modules = [
({ pkgs, ... }: {
nixpkgs.overlays = [ claude-desktop.overlays.default ];
environment.systemPackages = [ pkgs.claude-desktop ];
})
];
};
};
}
```
### Using Pre-built Releases
2025-01-17 11:17:57 +01:00
Download the latest `.deb`, `.rpm`, or `.AppImage` from the [Releases page](https://github.com/aaddrick/claude-desktop-debian/releases).
### Building from Source
See [docs/BUILDING.md](docs/BUILDING.md) for detailed build instructions.
2025-04-03 01:27:06 -04:00
## Configuration
2025-04-03 01:27:06 -04:00
Model Context Protocol settings are stored in:
```
~/.config/Claude/claude_desktop_config.json
```
For additional configuration options including environment variables and Wayland support, see [docs/CONFIGURATION.md](docs/CONFIGURATION.md).
2025-03-29 02:31:42 -04:00
## Troubleshooting
feat: KVM/bwrap isolation backends for cowork mode (#269) * feat: add KVM/bwrap isolation backends for cowork mode Refactor cowork-vm-service.js from monolithic VMManager into a pluggable backend architecture with three isolation levels: - HostBackend: direct execution on host (existing Phase 1 behavior) - BwrapBackend: bubblewrap namespace sandbox (PID/mount isolation) - KvmBackend: full QEMU/KVM VM with vsock, virtiofs, QMP monitor Backend auto-detected at startup (KVM > bwrap > host) or overridden via COWORK_VM_BACKEND env var. Shared helpers extracted for env filtering, arg cleanup, command resolution, and work dir handling. Also: - Add Cowork Mode section to --doctor diagnostics with per-tool checks and distro-specific install hints - Update Patch 4 to extract real win32 file entries for linux bundle manifest (enables app download infrastructure) - Update handover documentation for Phase 2/3 architecture Co-Authored-By: Claude <claude@anthropic.com> * fix: correct KvmBackend vsock port, direction, and kernel cmdline The guest sdk-daemon connects TO the host (CID=2), not the other way around. Confirmed via disassembly of the guest binary: the vsock port is 51234 (0xC822), matching the Hyper-V GUID in the Windows service. - Change VSOCK_GUEST_PORT from 2222 to 51234 - Reverse socat bridge: VSOCK-LISTEN (host listens) instead of VSOCK-CONNECT (host connecting to guest) - Add bridge server to accept persistent guest connection and route events/responses via callback map - Fix kernel cmdline: root=LABEL=cloudimg-rootfs (matches fstab) Co-Authored-By: Claude <claude@anthropic.com> * fix: patch VM download to use disk-backed temp dir on Linux Linux systems often mount /tmp as a small tmpfs (3-4GB). The VM rootfs download decompresses to ~9GB, causing ENOSPC. Patch the app's mkdtemp("wvm-") call to use the bundle directory (on real disk) instead of os.tmpdir() on Linux. Uses regex-based dynamic variable extraction to remain version-agnostic across minified code changes. Co-Authored-By: Claude <claude@anthropic.com> * fix: handle stale cowork socket (ECONNREFUSED) on Linux Stale sockets from previous sessions give ECONNREFUSED instead of ENOENT, bypassing the retry loop and auto-launch entirely. Fix: - Expand ENOENT check in retry loop to include ECONNREFUSED on Linux - Add cleanup_stale_cowork_socket() to launcher scripts (all formats) that removes dead sockets before Electron starts - Increase tmpdir patch search window from 1000 to 2000 chars Co-Authored-By: Claude <claude@anthropic.com> * fix: preserve DNS resolution inside bwrap sandbox On systems using systemd-resolved, /etc/resolv.conf is a symlink to /run/systemd/resolve/stub-resolv.conf. The bwrap --tmpfs /run option wiped this out, breaking DNS resolution inside the sandbox and preventing the spawned Claude process from reaching the API. Bind-mount the resolved /run/systemd/resolve/ directory back into the sandbox as read-only to restore DNS. Co-Authored-By: Claude <claude@anthropic.com> * fix: harden cowork isolation and build patches - Remove broken _setupEventForwarding (events already flow through _handleGuestData); the second bridge connection was silently ignored - Mount $HOME read-only in bwrap sandbox; only workDir and explicit mounts are writable (prevents writes to ~/.ssh, ~/.gnupg, etc.) - Scope Patch 4 win32 extraction to actual win32:{} block via brace counting to avoid crossing into darwin/linux sections - Set _qmpAvailable flag on QMP timeout instead of silently continuing - Wrap CID allocation at 65535 to prevent unbounded growth - Use execFileSync instead of execSync('which ...') in detectBackend - Coerce response ID to String for Map lookup in _handleGuestData - Use non-greedy [\s\S]*? in Patch 6 regex for nested brace robustness - Update patch count threshold from 4 to 5 after adding Patch 8 - Add age-based fallback for stale socket cleanup when socat is missing - Use indexOf-based splice in Patch 8 instead of string.replace() - Extract shared resolveSdkBinary helper to deduplicate SDK resolution - Remove dead retryFuncRe/retryFuncMatch variables from Patch 6 Co-Authored-By: Claude <claude@anthropic.com> * fix: address security and correctness issues from code review - Replace execSync string interpolation with execFileSync for qemu-img calls to eliminate shell injection risk - Add path validation to readFile in both LocalBackend and KvmBackend to restrict reads to within the user's home directory - Fix QMP _sendQmpCommand timer leak by clearing timeout on success - Fix _pendingCallbacks.delete() to use String(msg.id) matching the String(msg.id) used in the .get() lookup - Extract FORWARDED_EVENTS constant, cleanup helper, extractBlock helper, and consolidate doctor tool checks (from simplifier pass) Co-Authored-By: Claude <claude@anthropic.com> * docs: update README cowork notice with isolation backends and doctor info Co-Authored-By: Claude <claude@anthropic.com> --------- Co-authored-by: Claude <claude@anthropic.com>
2026-02-28 21:13:09 -05:00
Run `claude-desktop --doctor` for built-in diagnostics that check common issues (display server, sandbox permissions, MCP config, stale locks, and more). It also reports cowork mode readiness — which isolation backend will be used, and which dependencies (KVM, QEMU, vsock, socat, virtiofsd, bubblewrap) are installed or missing.
For additional troubleshooting, uninstallation instructions, and log locations, see [docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md).
2025-04-03 01:27:06 -04:00
## Acknowledgments
2025-04-03 01:27:06 -04:00
This project was inspired by [k3d3's claude-desktop-linux-flake](https://github.com/k3d3/claude-desktop-linux-flake) and their [Reddit post](https://www.reddit.com/r/ClaudeAI/comments/1hgsmpq/i_successfully_ran_claude_desktop_natively_on/) about running Claude Desktop natively on Linux.
2025-04-03 01:27:06 -04:00
Special thanks to:
- **k3d3**
- Original NixOS implementation
- Native bindings insights
- **[emsi](https://github.com/emsi/claude-desktop)**
- Title bar fix
- Alternative implementation approach
- **[leobuskin](https://github.com/leobuskin/unofficial-claude-desktop-linux)** for the Playwright-based URL resolution approach
- **[yarikoptic](https://github.com/yarikoptic)**
- Codespell support
- Shellcheck compliance
- **[IamGianluca](https://github.com/IamGianluca)** for build dependency check improvements
- **[ing03201](https://github.com/ing03201)** for IBus/Fcitx5 input method support
- **[ajescudero](https://github.com/ajescudero)** for pinning @electron/asar for Node compatibility
- **[delorenj](https://github.com/delorenj)** for Wayland compatibility support
- **[Regen-forest](https://github.com/Regen-forest)** for suggesting Gear Lever as AppImageLauncher replacement
- **[niekvugteveen](https://github.com/niekvugteveen)** for fixing Debian packaging permissions
- **[speleoalex](https://github.com/speleoalex)** for native window decorations support
- **[imaginalnika](https://github.com/imaginalnika)** for moving logs to `~/.cache/`
- **[richardspicer](https://github.com/richardspicer)** for the menu bar visibility fix on Linux
- **[jacobfrantz1](https://github.com/jacobfrantz1)**
- Claude Desktop code preview support
- Quick window submit fix
- **[janfrederik](https://github.com/janfrederik)** for the `--exe` flag to use a local installer
- **[MrEdwards007](https://github.com/MrEdwards007)** for discovering the OAuth token cache fix
fix(lifecycle): hide main window to tray on close, Linux (#451) * fix(lifecycle): hide main window to tray on close, Linux (#448) Electron's default window-all-closed handler quits the app on Linux. The existing tray icon and Ctrl+Q patches keep the app reachable while a window is alive, but as soon as the last window is closed (stray click on X, or a sign-out flow that closes mainWindow) the app exits and the tray goes with it — taking any in-app schedulers / MCP servers / cron tasks (/schedule skill) down silently until the user re-launches. Intercept BrowserWindow.close on main windows (not popups; Quick Entry and About already dismiss via hide(), never emit close) and preventDefault + hide unless app is in a real quit path. The quit path is detected via before-quit: Ctrl+Q, tray Quit, cmd+Q, SIGTERM and app.quit() from anywhere all emit before-quit, which arms app._quittingIntentionally so the close handler lets the window actually close. Gated by CLOSE_TO_TRAY, default on. Set CLAUDE_QUIT_ON_CLOSE=1 to restore the Electron-default behaviour. Fixes #448 Co-Authored-By: Claude <claude@anthropic.com> * fix(frame-fix-wrapper): drop superseded globalShortcut Ctrl+Q Removes the globalShortcut.register('CommandOrControl+Q') block that #484 superseded with the per-window webContents.on('before-input-event') listener. Auto-merging main into this branch left both registrations in place, which would re-introduce the AZERTY physical-keycode grab and system-wide shortcut steal that #484 fixed. The focus-scoped listener already covers the original #321 hidden-menu-bar use case. Also updates the close-to-tray comment to reference the new listener path instead of the removed global shortcut. Co-Authored-By: Claude <claude@anthropic.com> * docs(readme): credit lizthegrey for close-to-tray contribution Co-Authored-By: Claude <claude@anthropic.com> --------- Co-authored-by: Claude <claude@anthropic.com> Co-authored-by: aaddrick <aaddrick@gmail.com>
2026-04-28 15:16:56 -07:00
- **[lizthegrey](https://github.com/lizthegrey)**
- Version update contributions
- Close-to-tray on Linux to keep in-app schedulers, MCP servers, and the tray icon alive across window close
fix(autostart): route openAtLogin through XDG Autostart on Linux (#450) * fix(autostart): route openAtLogin through XDG Autostart on Linux (#128) Electron's app.getLoginItemSettings()/setLoginItemSettings() are no-ops on Linux (electron/electron#15198), so the "Run on startup" toggle never persists and isStartupOnLoginEnabled() returns undefined, failing the IPC handler's typeof === 'boolean' check. Intercept both calls in frame-fix-wrapper.js and back them with ~/.config/autostart/claude-desktop.desktop, which is honoured by GNOME/KDE/XFCE/Cinnamon/MATE/LXQt (XDG Autostart spec). Also coerce executableWillLaunchAtLogin (Windows-only in Electron, undefined on Linux) to a boolean so the IPC handler stops throwing. Fixes #128 Co-Authored-By: Claude <claude@anthropic.com> * fix(autostart): address review — APPIMAGE runtime target, XDG_CONFIG_HOME, StartupWMClass (#128) Addresses review comments on #450: - Resolve Exec= and Icon= at toggle time via process.env.APPIMAGE so AppImage users (who don't have claude-desktop on $PATH unless integrated via AppImageLauncher) get an autostart entry that launches the actual .AppImage bundle instead of a broken binary reference. escapeExecArg() handles Desktop Entry Exec escaping (quote + backslash-escape reserved chars). - Honour $XDG_CONFIG_HOME when set and non-empty, falling back to ~/.config only otherwise. Home-manager and dotfile users who relocate the config root were getting the entry dropped in the wrong place silently. - Add StartupWMClass=Claude to the generated entry, matching the value set by scripts/packaging/{deb,rpm}.sh, so DEs group the autostarted window with user-launched instances under a single taskbar/dock item. Drop Categories= per review guidance (autostart parsers ignore it). - Comment why opts.path is intentionally ignored: process.execPath points at the electron binary, not the launcher shim that sets ELECTRON_FORCE_IS_PACKAGED / ozone flags / orphan cleanup — honouring opts.path would write a broken autostart entry. The "removed" log placement (review item 4) is already inside the inner try, so unlinkSync throwing ENOENT short-circuits before the log runs. Left as-is. Co-Authored-By: Claude <claude@anthropic.com> * docs(readme): credit lizthegrey for XDG Autostart contribution Co-Authored-By: Claude <claude@anthropic.com> --------- Co-authored-by: Claude <claude@anthropic.com> Co-authored-by: aaddrick <aaddrick@gmail.com>
2026-04-28 16:02:47 -07:00
- "Run on startup" persistence on Linux via XDG Autostart, fixing the toggle that would silently revert
- **[mathys-lopinto](https://github.com/mathys-lopinto)**
- AUR package
- Automated deployment
- **[pkuijpers](https://github.com/pkuijpers)** for root cause analysis of the RPM repo GPG signing issue
- **[dlepold](https://github.com/dlepold)** for identifying the tray icon variable name bug with a working fix
- **[Voork1144](https://github.com/Voork1144)**
- Detailed analysis of the tray icon minifier bug
- Root-cause analysis of the Chromium layout cache bug
- Direct child `setBounds()` fix approach
- **[sabiut](https://github.com/sabiut)**
- `--doctor` diagnostic command
- SHA-256 checksum validation for downloads
- Post-build integration tests for deb, rpm, and AppImage artifacts
- **[milog1994](https://github.com/milog1994)**
- Popup detection
- Functional stubs
- Wayland compositor support
- **[jarrodcolburn](https://github.com/jarrodcolburn)**
- Passwordless sudo support in container/CI environments
- Identifying the gh-pages 4GB bloat fix
- Identifying the virtiofsd PATH detection issue on Debian
- Detailed analysis of the CI release pipeline failure caused by runner kills during compare-releases
- Diagnosing the session-start hook sudo blocking issue with three solution approaches
- **[chukfinley](https://github.com/chukfinley)** for experimental Cowork mode support on Linux
- **[CyPack](https://github.com/CyPack)**
- Orphaned cowork daemon cleanup on startup
- `COWORK_VM_BACKEND` documentation, Cowork troubleshooting sections, and unknown-value warning in `--doctor`
fix: update Linux tray icon in place on OS theme change (#515) * fix: update Linux tray icon in place on OS theme change Avoids a StatusNotifierItem re-registration race on KDE Plasma where the old SNI remains registered when the new one appears, resulting in two tray icons side by side until session logout. `patch_tray_menu_handler` already bounds the race with a 250 ms delay after `tray.destroy()`, but that's not enough on all setups (reproduced on Fedora 43 KDE Plasma 6.6.4 + Wayland). Widening the delay just moves the goalposts; the race is structural. Fix: inject a fast-path before the existing destroy+recreate block in the tray rebuild function. When the tray already exists and isn't being disabled, update its icon and context menu in place via `setImage` + `setContextMenu` — the existing StatusNotifierItem stays registered, no DBus re-registration, no race. The slow path (destroy + delay + re-create) is kept for the initial creation and the tray-disable cases where it's unavoidable. All five minified locals needed by the fast-path (tray function, tray variable, electron module, menu function, icon path const, menuBarEnabled flag) are extracted dynamically; the idempotency guard re-keys off the post-rename `setImage(...)` sequence. Triggered in KDE System Settings by any of Appearance → Colors / Plasma Style / Global Theme, which all fire the same `nativeTheme.on('updated')` signal. Follow-up to #491. The broader submenu work from that PR stays parked on features/change-icon-color pending the scope discussion in #492; this PR ships only the duplicate-tray-icon fix that @aaddrick asked to split out. Co-Authored-By: Claude <claude@anthropic.com> * fix(tray): tighten in-place patch extraction guards Drop the redundant `electron_var_re_local` local — `electron_var_re` is already a sourced global from `_common.sh` with the same value. Replace the silent `head -1` on `enabled_var` extraction with an explicit count-and-bail. The grep matches `const X=fn("menuBarEnabled")` across the whole file; today there's exactly one site (inside the tray function), but if upstream ever ships a second the previous code would silently bind to whichever the minifier emitted first. Bail loudly with a count diagnostic instead. Verified on the live 1.3883.0 build asar: all five extractions resolve (`Nh`/`wAt`/`t`/`e`) — note the symbol drift vs. the build-reference's `fh`/`CZe`. Fast-path injects, JS validates, idempotent re-run confirmed, duplicate-icon repro gone on Nobara KDE Plasma 6 (Wayland) under Appearance → Colors / Plasma Style / Global Theme. Co-Authored-By: Claude <claude@anthropic.com> * docs(readme): credit @IliyaBrook for tray duplicate-icon fix Co-Authored-By: Claude <claude@anthropic.com> --------- Co-authored-by: Claude <claude@anthropic.com> Co-authored-by: aaddrick <aaddrick@gmail.com>
2026-04-27 16:19:36 +03:00
- **[IliyaBrook](https://github.com/IliyaBrook)**
- Fixing the platform patch for Claude Desktop >= 1.1.3541 arm64 refactor
- Fixing the duplicate tray icon on OS theme change with an in-place `setImage`/`setContextMenu` fast-path that avoids the KDE Plasma SNI re-registration race
- **[MichaelMKenny](https://github.com/MichaelMKenny)**
- Diagnosing the `$`-prefixed electron variable bug
- Root cause analysis and workaround
- **[daa25209](https://github.com/daa25209)** for detailed root cause analysis of the cowork platform gate crash and patch script
- **[noctuum](https://github.com/noctuum)**
- `CLAUDE_MENU_BAR` env var with configurable menu bar visibility
- Boolean alias support
- **[typedrat](https://github.com/typedrat)**
- NixOS flake integration with build.sh
- node-pty derivation
- CI auto-update
- Fixing the flake package scoping regression
- **[cbonnissent](https://github.com/cbonnissent)**
- Reverse-engineering the Cowork VM guest RPC protocol
- Fixing the KVM startup blocker
- Fixing RPC response id echoing for persistent connections
- Configurable bwrap mount points via a dedicated Linux config file
- `{src, dst}` mount form in `coworkBwrapMounts` for distinct host/sandbox paths (e.g. persistent `/tmp` across Bash tool calls)
- **[joekale-pp](https://github.com/joekale-pp)** for adding `--doctor` support to the RPM launcher
- **[ecrevisseMiroir](https://github.com/ecrevisseMiroir)** for the bwrap backend sandbox isolation with tmpfs-based minimal root
- **[arauhala](https://github.com/arauhala)** for detailed root cause analysis of the NixOS `isPackaged` regression
- **[cromagnone](https://github.com/cromagnone)** for confirming the VM download loop on bwrap installs with detailed logs that disproved the initial triage
- **[aHk-coder](https://github.com/aHk-coder)** for diagnosing the hardcoded minified variable crash in the cowork smol-bin patch
- **[RayCharlizard](https://github.com/RayCharlizard)**
- Detailed analysis of the self-referential `.mcpb-cache` symlink ELOOP bug
- Fixing auto-memory path translation on HostBackend
- Fixing the `ion-dist` static asset copy for the `app://` protocol handler
- **[reinthal](https://github.com/reinthal)** for fixing the NixOS build breakage caused by the nixpkgs `nodePackages` removal
- **[gianluca-peri](https://github.com/gianluca-peri)**
- Reporting the GNOME quit accessibility issue
- Confirming tray behavior with AppIndicator
fix: launcher-common.sh self-match and stale socket cleanup (#407) (#425) * fix: launcher-common.sh self-match and stale socket cleanup (#407) Three related bugs in scripts/launcher-common.sh that combine to break Claude Desktop startup after any crash that reparents the cowork daemon on Debian/Ubuntu/Mint systems. 1. cleanup_orphaned_cowork_daemon — the old pgrep pattern 'claude-desktop' self-matches the launcher's own bash process (cmdline `bash /usr/bin/claude-desktop`), causing the function to return early on every invocation. The SIGTERM loop never runs. Replaced with `pgrep -f 'app\.asar'` plus $$/$PPID exclusion, --type= filter (skips chromium helpers), and /proc/*/status check (skips stopped/zombie launcher bashes). Added SIGKILL escalation after ~2s so cleanup_stale_cowork_socket reliably sees no daemon. 2. cleanup_stale_cowork_socket — the old implementation required socat (not preinstalled on Debian/Ubuntu/Mint) and fell through to a find -mmin +1440 check that ignored any socket younger than 24h. Rewritten to use the ordering invariant: cleanup_orphaned_cowork_daemon runs first and kills any orphan, so at this point an extant daemon proves the socket is live and an absent daemon proves the socket is stale. No socat dependency. 3. run_doctor orphan check — same self-match flaw as (1). claude-desktop --doctor reported [PASS] Cowork daemon: running (parent alive) on systems with a genuine orphan, actively misleading users trying to diagnose this failure. Applied the same detection primitive as (1). Complements #410 (daemon-side crash recovery): #410 reduces how often orphans are created; this ensures the launcher actually cleans them up when they are. Fixes #407 Co-Authored-By: martin152 <martin152@users.noreply.github.com> Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * docs: credit martin152 in Acknowledgments for #407 launcher fix Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * style: quote RHS of $$/$PPID comparisons (SC2053) Shellcheck SC2053: quote RHS in [[ ]] equality tests to prevent glob matching. No behavior change — $$ and $PPID are always numeric PIDs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: martin152 <martin152@users.noreply.github.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-18 19:28:22 -05:00
- **[martin152](https://github.com/martin152)** for detailed diagnosis and a complete patch for three launcher cleanup bugs: `cleanup_orphaned_cowork_daemon` self-match, `cleanup_stale_cowork_socket` socat dependency no-op, and the same self-match in `--doctor`
fix: diagnose AppArmor userns block on bwrap probe (#351) (#434) * fix: diagnose AppArmor userns block on bwrap probe (#351) Ubuntu 24.04+ ships apparmor_restrict_unprivileged_userns=1 by default, which blocks the user namespace bwrap needs to start. The daemon's probe then fails, auto-detect silently falls through to KVM, and KVM hangs waiting for a rootfs the user hasn't set up — leaving Cowork stuck in a retry loop with no clear error. - Classify the probe failure (classifyBwrapProbeError) so the daemon can distinguish AppArmor/userns blocks from generic failures and log a pointer to the TROUBLESHOOTING.md remediation. - Stop falling through to KVM when bwrap is installed but blocked; drop to host-direct instead so users see a working (if unsandboxed) Cowork and the reason bwrap didn't engage. Users who actually want KVM can still set COWORK_VM_BACKEND=kvm. - Mirror the probe + diagnosis in `--doctor` so misconfigured systems get the same actionable output without waiting for a daemon log. - Document the AppArmor profile workaround in TROUBLESHOOTING.md. - Credit @hfyeh for the diagnosis and profile snippet. Co-Authored-By: Claude <claude@anthropic.com> * refactor: simplify PR #434 per cdd-code-simplifier Drop redundant `-n` guard around the COWORK_VM_BACKEND case in `--doctor`: the `${VAR,,}` expansion is already safe on an unset var (no `set -u` in this script) and the `kvm|host` arms simply don't match an empty string. Co-Authored-By: Claude <claude@anthropic.com> --------- Co-authored-by: Claude <claude@anthropic.com>
2026-04-19 01:12:13 -05:00
- **[hfyeh](https://github.com/hfyeh)** for diagnosing the Ubuntu 24.04 AppArmor unprivileged-userns block on Cowork bwrap and contributing the AppArmor profile workaround
- **[davidamacey](https://github.com/davidamacey)** for identifying and fixing the XRDP GPU compositing blank-window issue on remote desktop sessions
fix(cowork): forward CLAUDE_CODE_OAUTH_TOKEN to VM spawn env (#482) (#485) * fix(cowork): forward CLAUDE_CODE_OAUTH_TOKEN to VM spawn env (#482) buildSpawnEnv and BwrapBackend.spawn both stripped every CLAUDE_CODE_* env var from the daemon's process.env via filterEnv(process.env, ['CLAUDE_CODE_']) -- including CLAUDE_CODE_OAUTH_TOKEN, the standard auth channel for the in-VM claude binary. The bwrap sandbox mounts home as an empty --dir, so ~/.claude/.credentials.json is inaccessible inside; env is the only viable auth path. Result: every shell tool call returned "Not logged in. Please run /login". Upstream's seA() does include the token in the spawn env it assembles, but on Linux that payload isn't reaching the daemon's params.env, so the daemon's inherited process.env is the only surviving source. Stripping it severed auth. Introduce FORWARDED_ENV_KEYS = ['CLAUDE_CODE_OAUTH_TOKEN'] plus a forwardAuthEnv helper that re-adds the token from process.env when appEnv doesn't provide one. Extract buildBaseSpawnEnv as the shared env-construction path for both spawn sites so the filter/forward logic can't drift. Diagnosis and reference diff by @pb3ck in #482. This PR extends the fix to BwrapBackend.spawn (the second call site), factors the shared helper, and adds regression coverage. Co-Authored-By: Claude <claude@anthropic.com> * refactor(cowork): fold forwardAuthEnv into buildBaseSpawnEnv The forwardAuthEnv helper had a single call site inside buildBaseSpawnEnv. Inlining the forward loop removes a layer of indirection for a private helper and consolidates the empty-string guard comment next to the code it documents. No behavior change. All 72 tests in cowork-path-translation.bats still pass, including the four #482 regression tests. Co-Authored-By: Claude <claude@anthropic.com> * docs(readme): credit @pb3ck for #482 diagnosis Co-Authored-By: Claude <claude@anthropic.com> --------- Co-authored-by: Claude <claude@anthropic.com>
2026-04-21 16:33:59 -05:00
- **[pb3ck](https://github.com/pb3ck)** for diagnosing the Cowork `CLAUDE_CODE_OAUTH_TOKEN` env-strip bug with a working reference diff
fix(cowork.sh): allow $ in minified identifier anchors; defensive lastIndexOf (#555) Two regex anchors in patch_cowork_linux() used \w+ to capture minified identifiers, but on Claude Desktop 1.5354.0 those identifiers contain $ (e.g. C$i, g$i). \w excludes $, so the inner captures never matched: - Patch 2b (vm: module assignment) silently no-op'd — no warning, no failure. Build log went from "Applied 12" to "Applied 10". - Patch 6 step 2 (retry-delay auto-launch) emitted a warning but still failed to apply. Either way, the resulting app.asar shipped half-patched and Cowork startup failed at runtime with "Swift VM addon not available". The fix widens both inner captures from \w+ to [\w$]+, matching the existing precedent at scripts/patches/cowork.sh:482-501 (introduced in PR #421 for the $e fs-reference rename in 1.3109.0). Also switches Patch 6 from indexOf to lastIndexOf for the "VM service not running" anchor — defensive against future versions reintroducing the string outside the retry-loop site. Verified end-to-end on Fedora 43 / KDE Plasma 6 / Wayland: build log shows "Applied 12 cowork patches"; daemon auto-launches at startup with clean lifecycle (startup → listen → SIGTERM exit code=0). Follow-ups tracked in #559. Resolves #558. Likely resolves #553 (named symptom) and #445 (daemon never auto-spawned on Linux). Co-authored-by: Joost-Maker <66303669+Joost-Maker@users.noreply.github.com> Co-authored-by: HumboldtJoker <19808525+HumboldtJoker@users.noreply.github.com> Co-authored-by: zabka <3833286+zabka@users.noreply.github.com>
2026-05-02 14:53:02 +02:00
- **[Joost-Maker](https://github.com/Joost-Maker)** for fixing the `$e` fs reference crash in cowork Patch 9 on Claude Desktop 1.3109.0, introducing the `[$\w]+` identifier-capture pattern at `cowork.sh:482-501` (#421)
- **[aJV99](https://github.com/aJV99)** for exporting `GDK_BACKEND=wayland` in native Wayland mode to fix XWayland fallback blur on HiDPI displays
- **[Andrej730](https://github.com/Andrej730)**
- Quick-window regex readability refactor (`String.raw` + `escapeRegExp` helper)
fix(cowork.sh): allow $ in minified identifier anchors; defensive lastIndexOf (#555) Two regex anchors in patch_cowork_linux() used \w+ to capture minified identifiers, but on Claude Desktop 1.5354.0 those identifiers contain $ (e.g. C$i, g$i). \w excludes $, so the inner captures never matched: - Patch 2b (vm: module assignment) silently no-op'd — no warning, no failure. Build log went from "Applied 12" to "Applied 10". - Patch 6 step 2 (retry-delay auto-launch) emitted a warning but still failed to apply. Either way, the resulting app.asar shipped half-patched and Cowork startup failed at runtime with "Swift VM addon not available". The fix widens both inner captures from \w+ to [\w$]+, matching the existing precedent at scripts/patches/cowork.sh:482-501 (introduced in PR #421 for the $e fs-reference rename in 1.3109.0). Also switches Patch 6 from indexOf to lastIndexOf for the "VM service not running" anchor — defensive against future versions reintroducing the string outside the retry-loop site. Verified end-to-end on Fedora 43 / KDE Plasma 6 / Wayland: build log shows "Applied 12 cowork patches"; daemon auto-launches at startup with clean lifecycle (startup → listen → SIGTERM exit code=0). Follow-ups tracked in #559. Resolves #558. Likely resolves #553 (named symptom) and #445 (daemon never auto-spawned on Linux). Co-authored-by: Joost-Maker <66303669+Joost-Maker@users.noreply.github.com> Co-authored-by: HumboldtJoker <19808525+HumboldtJoker@users.noreply.github.com> Co-authored-by: zabka <3833286+zabka@users.noreply.github.com>
2026-05-02 14:53:02 +02:00
- Fixing the visibility-function regex break on Claude Desktop 1.3883.0 (#496)
- **[HumboldtJoker](https://github.com/HumboldtJoker)** for diagnosing the cowork Patch 2b silent failure on Claude Desktop 1.5354.0 — identifying that the log line was patched but session init still routed through the Swift addon (#553)
- **[zabka](https://github.com/zabka)** for identifying that `cowork-vm-service.js` was never auto-spawned on Linux and contributing a systemd-unit workaround that scoped the daemon auto-launch fix (#445)
- **[sirfaber](https://github.com/sirfaber)** for fixing the `$`-in-minified-identifier breakage of cowork Patch 2b (vm module assignment) and Patch 6 step 2 (retry-delay auto-launch) on Claude Desktop 1.5354.0 (#555)
- **[ProfFlow](https://github.com/ProfFlow)** for re-fixing the RPM repodata signing regression by appending `!` to the keyid passed to `gpg --default-key`, forcing `repomd.xml` to be signed by the primary key instead of the auto-selected signing subkey (#566)
- **[jslatten](https://github.com/jslatten)** for fixing the KDE Plasma Wayland launcher-grouping bug by setting `pkg.desktopName` in the packaged `app.asar`'s `package.json`, format-conditional so deb/rpm get `claude-desktop.desktop` and AppImage gets `io.github.aaddrick.claude-desktop-debian.desktop` (#562)
- **[JoshuaVlantis](https://github.com/JoshuaVlantis)**
- RPM `chrome-sandbox` SUID via `%attr(4755, ...)` instead of a `%post` chmod scriptlet so the bit survives `--noscripts` and layered images (#539)
- `autoUpdater` no-op Proxy on Linux that defends against future feed activation, with a thenable allowlist masking `then`/`catch`/`finally`/`Symbol.toPrimitive`/`Symbol.iterator` to `undefined` (#567)
- Failing loudly on `npm install node-pty` failures instead of silently shipping the upstream Windows binaries, plus auto-installing `gcc`/`g++`/`make`/`python3` on minimal build environments (#401)
- **[Hayao0819](https://github.com/Hayao0819)** for diagnosing the upstream `titleBarStyle:""``titleBarStyle:"hiddenInset"` migration that broke the About window render on GNOME/X11 and contributing the `isPopupWindow()` match extension (#481, #489)
## Sponsorship
If this project is useful to you, consider [sponsoring on GitHub](https://github.com/sponsors/aaddrick).
## License
The build scripts in this repository are dual-licensed under:
- MIT License (see [LICENSE-MIT](LICENSE-MIT))
- Apache License 2.0 (see [LICENSE-APACHE](LICENSE-APACHE))
The Claude Desktop application itself is subject to [Anthropic's Consumer Terms](https://www.anthropic.com/legal/consumer-terms).
## Privacy
This repository uses an automated triage bot that sends issue contents to Anthropic's API for classification and investigation when you file a bug report or feature request. The bot reads the issue body, title, and any referenced related issues; it does not follow URLs, execute code blocks, or read content outside the triggering issue.
Do not include credentials, tokens, personal data, or anything you wouldn't put on a public issue tracker. If you post sensitive content and then edit it out, the bot's original read is preserved as a run artifact for audit — GitHub's UI hides the edit, but the bot's view of what you wrote is recoverable by maintainers.
Full design and data inventory: [`docs/issue-triage/README.md`](docs/issue-triage/README.md).
## Contributing
2025-08-09 21:58:44 -04:00
Contributions are welcome! By submitting a contribution, you agree to license it under the same dual-license terms as this project.