Files

452 lines
20 KiB
Markdown
Raw Permalink Normal View History

# Claude Desktop Debian - Development Notes
## Project Overview
This project repackages Claude Desktop (Electron app) for Debian/Ubuntu Linux, applying necessary patches for Linux compatibility.
## Learnings
The [`docs/learnings/`](docs/learnings/) directory contains hard-won technical knowledge from debugging and fixing issues — things that aren't obvious from reading the code or docs alone. Consult these before working on related areas. Add new entries when you discover something non-obvious that would save future contributors (human or AI) significant time.
- [`nix.md`](docs/learnings/nix.md) — NixOS packaging, Electron resource path resolution, testing without NixOS
- [`cowork-vm-daemon.md`](docs/learnings/cowork-vm-daemon.md) — Cowork VM daemon lifecycle, respawn logic, crash diagnosis
- [`plugin-install.md`](docs/learnings/plugin-install.md) — Anthropic & Partners plugin install flow, gate logic, backend endpoints, and DevTools recipes
- [`apt-worker-architecture.md`](docs/learnings/apt-worker-architecture.md) — APT/DNF binary distribution via Cloudflare Worker + GitHub Releases, redirect chain, credential ownership, heartbeat runbook
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
- [`tray-rebuild-race.md`](docs/learnings/tray-rebuild-race.md) — why destroy + recreate on `nativeTheme` updates briefly duplicates the tray icon on KDE Plasma, and the in-place `setImage` + `setContextMenu` fast-path that avoids the SNI re-registration race
docs(learnings): document MCP double-spawn upstream bug (#526) (#527) * docs(learnings): document MCP double-spawn upstream bug (#526) Captures the reporter's root-cause analysis for issue #526: stdio MCP servers in claude_desktop_config.json get spawned twice when both the chat panel and the Code/Agent (Cowork) panel are active. The duplication happens entirely in upstream Anthropic Claude Desktop main (LocalSessions and LocalAgentModeSessions each hold an independent Claude Agent SDK query whose stdio transport bypasses the global hZ MCP registry). Includes verification that this packaging is not implicated, the lockfile + idempotent-write workaround pattern for affected MCP authors, and routing guidance for upstream reports. Co-Authored-By: Claude <claude@anthropic.com> * docs(learnings): simplifier pass on MCP double-spawn entry Drop redundant "Anthropic" qualifier in Status section and reword CLAUDE.md index bullet to noun-phrase form matching siblings. Co-Authored-By: Claude <claude@anthropic.com> * docs(learnings): apply review fixes from #527 - Fix `LocalAgentModeSessions` IPC namespace: add missing `_$_` separator (was `claude.web_$_LocalAgentModeSessions_*`, should be `claude.web_$_LocalAgentModeSessions_$_*`). Verified against the channel names in the actual minified source. - Add back the `Logs prefix` column (`[CCD]` / `[LAM]`) the original issue body had — these are the literal grep targets in `~/.config/Claude/logs/` for confirming the bug hit. - Re-route the secondary upstream venue from `anthropics/claude-code` to `anthropics/claude-agent-sdk-typescript`. The SDK transport (`spawnLocalProcess` / `Du.spawn`) lives in the SDK's own public repo (issues enabled); pointing at `claude-code` while saying the CLI isn't on the spawn path is the exact contradiction the warning paragraph below it tries to prevent. - Workaround note: reclaim a stale lock via `rename()` over the path, not `unlink()` then re-open. Heads off the obvious-but-racy port for anyone copying the pattern. Co-Authored-By: Claude <claude@anthropic.com> --------- Co-authored-by: Claude <claude@anthropic.com>
2026-04-30 22:51:08 -05:00
- [`mcp-double-spawn.md`](docs/learnings/mcp-double-spawn.md) — Stdio MCPs spawn 2× when chat and Code/Agent panels are both active, root cause in upstream session managers, MCP-author workaround
feat(linux): hybrid titlebar mode for clickable in-app topbar (#538) * feat(linux): hybrid titlebar mode for clickable in-app topbar Default `CLAUDE_TITLEBAR_STYLE` is now `hybrid`: native OS frame plus a BrowserView preload shim that convinces claude.ai's bundle to render its in-app topbar (hamburger / sidebar / search / nav / Cowork ghost). Stacked layout instead of Windows's combined bar, but every button is clickable. Why not the upstream `frame:false` + WCO config: investigation (see docs/learnings/linux-topbar-shim.md) ruled out `titleBarOverlay`, `titleBarStyle:'hidden'`, and the `.draggable` CSS class as the source of the topbar click-eating drag region. The remaining cause is a Chromium-level implicit drag region for `frame:false` windows that exists on both X11 and Wayland and has no Electron-API knob. With `frame:true` the OS handles dragging and Chromium pushes no drag-region map, so the buttons receive mouse events normally. Modes: - `hybrid` (default) — system frame + shim, topbar visible and clickable - `native` — system frame, no shim, no in-app topbar - `hidden` — frameless + WCO config, matches Windows/macOS upstream; topbar visible but not clickable on Linux. Kept for Wayland comparison and future investigation Tests: tests/launcher-common.bats grew 16 cases covering `_resolve_titlebar_style`, `build_electron_args` flag selection per mode, and `setup_electron_env` env-var wiring per mode. `claude-desktop --doctor` now reports the resolved mode and warns when `hidden` is set. Co-Authored-By: Claude <claude@anthropic.com> * docs(learnings): add hybrid-mode screenshot Visual reference of the stacked layout: DE-drawn titlebar on top with native window controls, claude.ai's in-app topbar (hamburger / search / back-forward) immediately below it. Co-Authored-By: Claude <claude@anthropic.com> * docs(learnings): fix codespell hit (Pre-emptive → Preemptive) Codespell flags hyphenated "Pre-emptive" as a misspelling of "Preemptive". Drops the hyphen to clear the spellcheck CI gate on PR #538. Co-Authored-By: Claude <claude@anthropic.com> --------- Co-authored-by: Claude <claude@anthropic.com>
2026-05-01 02:47:16 -04:00
- [`linux-topbar-shim.md`](docs/learnings/linux-topbar-shim.md) — why claude.ai's in-app topbar is missing on Linux, the four gates that hide it, why the upstream `frame:false` + WCO config has unclickable buttons on X11 (Chromium-level implicit drag region), and the resolution: hybrid mode (system frame + UA-spoof shim → stacked layout, full button functionality)
- [`test-harness-electron-hooks.md`](docs/learnings/test-harness-electron-hooks.md) — why constructor-level `BrowserWindow` wraps are silently bypassed by `frame-fix-wrapper`'s Proxy, and the prototype-method hook pattern that works (used by the Quick Entry test runners)
- [`test-harness-ax-tree-walker.md`](docs/learnings/test-harness-ax-tree-walker.md) — five non-obvious traps in the v7 fingerprint walker after the AX-tree migration: AX-enable async lag, navigateTo-to-same-URL no-op, claude.ai's flat `dialog>button[]` lists, the `more options for X` per-row shape, and sidebar virtualization vs the lookup-failure threshold
- [`patching-minified-js.md`](docs/learnings/patching-minified-js.md) — general lessons from maintaining a long-lived patch suite against an actively re-minified upstream: anchor selection (literals over identifiers), the `\w` vs `$` identifier-capture trap, beautified false-negatives, idempotency guards, multi-site coordination, non-unique anchor disambiguation, and the SHA-256-pinned hypothesis-verification recipe
## Code Style
All shell scripts in this project must follow the [Bash Style Guide](STYLEGUIDE.md). Key points:
- Tabs for indentation, lines under 80 characters (exception: URLs and regex patterns)
- Use `[[ ]]` for conditionals, `$(...)` for command substitution
- Single quotes for literals, double quotes for expansions
- Lowercase variables; UPPERCASE only for constants/exports
- Use `local` in functions, avoid `set -e` and `eval`
### Linting
Shell scripts are checked with `shellcheck` and GitHub Actions workflows with `actionlint` before pushing. When lint issues are found:
1. **Fix the code** - Correct the underlying issue rather than suppressing the warning
2. **Disable directives are a last resort** - Only use `# shellcheck disable=SCXXXX` when:
- The warning is a false positive
- The pattern is intentional and unavoidable
- Always add a comment explaining why the disable is needed
3. **Run `/lint` to check manually** - Use this skill to check for issues before pushing
## GitHub Workflow
### General Approach
- Use `gh` CLI for all GitHub interactions
- Create branches based on issue numbers: `fix/123-description` or `feature/123-description`
- Reference issues in commits and PRs with `#123` or `Fixes #123`
- After creating a PR, add a comment to the related issue with a summary and link to the PR
### Investigating Issues
For older issues, review the state of the code when the issue was raised - it may have already been addressed:
```bash
# Get issue creation date
gh issue view 123 --json createdAt
# Find the commit just before the issue was created
git log --oneline --until="2025-08-23T08:48:35Z" -1
# View a file at that point in time
git show <commit>:path/to/file.sh
# Search for relevant changes since the issue was created
git log --oneline --after="2025-08-23" -- path/to/file.sh
# View a specific commit that may have fixed the issue
git show <commit>
```
This helps identify if the issue was already fixed, and allows referencing the specific commit in the response.
### Attribution
**For PR descriptions**, include full attribution:
```
---
Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <model-name> <noreply@anthropic.com>
<XX>% AI / <YY>% Human
Claude: <what AI did>
Human: <what human did>
```
- Use the actual model name (e.g., `Claude Opus 4.5`, `Claude Sonnet 4`)
- The percentage split should honestly reflect the contribution balance for that specific work
- This provides a trackable record of AI-assisted development over time
**For issues and comments**, use simplified attribution:
```
---
Written by Claude <model-name> via [Claude Code](https://claude.ai/code)
```
**For commits**, include a Co-Authored-By trailer:
```
Co-Authored-By: Claude <claude@anthropic.com>
```
### Contributor Credits
The README Acknowledgments section credits external contributors in chronological order (by merge date or fix date). Update it when:
1. **Merging an external PR** — Add the author to the Acknowledgments list with a link to their GitHub profile and a brief description of their contribution.
2. **Implementing a fix suggested in an issue** — If an issue author (or commenter) provided a concrete fix, workaround, code snippet, or detailed technical analysis that was directly used, credit them too.
Contributors are listed in chronological order: inspirational projects first (k3d3, emsi, leobuskin), then contributors ordered by when their contribution was merged or implemented.
## Working with Minified JavaScript
### Important Guidelines
docs: refresh for scripts/ split layout Updates agent definitions, learnings, CLAUDE.md, and BUILDING.md so path references point at the new module files instead of the old monolithic build.sh. Agent definitions: .claude/agents/issue-triage.md — table of per-category investigation paths now points at scripts/patches/*.sh and scripts/packaging/*.sh instead of "build.sh (search patch_X)". .claude/agents/electron-linux-specialist.md — patching-functions table now includes each function's file location; directory tree illustration reflects the new scripts/ layout. Documentation: CLAUDE.md — "Working with Minified JavaScript" section points at scripts/patches/*.sh; frame-fix injection attributed to scripts/patches/app-asar.sh; the version-bump checks now grep scripts/setup/detect-host.sh. docs/BUILDING.md — automated version detection paragraph now mentions scripts/setup/detect-host.sh as the file that holds the URLs. docs/learnings/cowork-vm-daemon.md — Patch 6 pointer now says scripts/patches/cowork.sh; line-number references dropped in favour of anchor-based search (line numbers drift between releases). docs/learnings/plugin-install.md — Key Files section points at scripts/patches/cowork.sh for patch_cowork_linux. Historical changelog-style references (e.g. docs/cowork-linux-handover.md describing what was "added to build.sh" during initial cowork work) are intentionally left unchanged — they describe a point-in-time state of the codebase. Co-Authored-By: Claude <claude@anthropic.com>
2026-04-20 07:31:02 -04:00
1. **Always use regex patterns** when modifying the source JavaScript. Patches live in `scripts/patches/*.sh` (one file per subsystem: `tray.sh`, `cowork.sh`, `claude-code.sh`, etc.); `build.sh` is only an orchestrator that sources them. Variable and function names are minified and **change between releases**.
2. **The beautified code in `build-reference/` has different spacing** than the actual minified code in the app. Patterns must handle both:
- Minified: `oe.nativeTheme.on("updated",()=>{`
- Beautified: `oe.nativeTheme.on("updated", () => {`
3. **Use `-E` flag with sed** for extended regex support when patterns need grouping or alternation.
docs: refresh for scripts/ split layout Updates agent definitions, learnings, CLAUDE.md, and BUILDING.md so path references point at the new module files instead of the old monolithic build.sh. Agent definitions: .claude/agents/issue-triage.md — table of per-category investigation paths now points at scripts/patches/*.sh and scripts/packaging/*.sh instead of "build.sh (search patch_X)". .claude/agents/electron-linux-specialist.md — patching-functions table now includes each function's file location; directory tree illustration reflects the new scripts/ layout. Documentation: CLAUDE.md — "Working with Minified JavaScript" section points at scripts/patches/*.sh; frame-fix injection attributed to scripts/patches/app-asar.sh; the version-bump checks now grep scripts/setup/detect-host.sh. docs/BUILDING.md — automated version detection paragraph now mentions scripts/setup/detect-host.sh as the file that holds the URLs. docs/learnings/cowork-vm-daemon.md — Patch 6 pointer now says scripts/patches/cowork.sh; line-number references dropped in favour of anchor-based search (line numbers drift between releases). docs/learnings/plugin-install.md — Key Files section points at scripts/patches/cowork.sh for patch_cowork_linux. Historical changelog-style references (e.g. docs/cowork-linux-handover.md describing what was "added to build.sh" during initial cowork work) are intentionally left unchanged — they describe a point-in-time state of the codebase. Co-Authored-By: Claude <claude@anthropic.com>
2026-04-20 07:31:02 -04:00
4. **Extract variable names dynamically** rather than hardcoding them. Shared extraction helpers live in `scripts/patches/_common.sh`. Example:
```bash
# Extract function name from a known pattern
TRAY_FUNC=$(grep -oP 'on\("menuBarEnabled",\(\)=>\{\K[$\w]+(?=\(\)\})' app.asar.contents/.vite/build/index.js)
```
5. **Handle optional whitespace** in regex patterns:
```bash
# Bad: assumes no spaces
sed -i 's/oe.nativeTheme.on("updated",()=>{/...'
# Good: handles optional whitespace
sed -i -E 's/(oe\.nativeTheme\.on\(\s*"updated"\s*,\s*\(\)\s*=>\s*\{)/...'
```
### Reference Files
- `build-reference/app-extracted/` - Extracted and beautified source for analysis
- `build-reference/tray-icons/` - Tray icon assets for reference
## Frame Fix Wrapper
The app uses a wrapper system to intercept and fix Electron behavior for Linux:
- **`frame-fix-wrapper.js`** - Intercepts `require('electron')` to patch BrowserWindow defaults (e.g., `frame: true` for proper window decorations on Linux)
- **`frame-fix-entry.js`** - Entry point that loads the wrapper before the main app
docs: refresh for scripts/ split layout Updates agent definitions, learnings, CLAUDE.md, and BUILDING.md so path references point at the new module files instead of the old monolithic build.sh. Agent definitions: .claude/agents/issue-triage.md — table of per-category investigation paths now points at scripts/patches/*.sh and scripts/packaging/*.sh instead of "build.sh (search patch_X)". .claude/agents/electron-linux-specialist.md — patching-functions table now includes each function's file location; directory tree illustration reflects the new scripts/ layout. Documentation: CLAUDE.md — "Working with Minified JavaScript" section points at scripts/patches/*.sh; frame-fix injection attributed to scripts/patches/app-asar.sh; the version-bump checks now grep scripts/setup/detect-host.sh. docs/BUILDING.md — automated version detection paragraph now mentions scripts/setup/detect-host.sh as the file that holds the URLs. docs/learnings/cowork-vm-daemon.md — Patch 6 pointer now says scripts/patches/cowork.sh; line-number references dropped in favour of anchor-based search (line numbers drift between releases). docs/learnings/plugin-install.md — Key Files section points at scripts/patches/cowork.sh for patch_cowork_linux. Historical changelog-style references (e.g. docs/cowork-linux-handover.md describing what was "added to build.sh" during initial cowork work) are intentionally left unchanged — they describe a point-in-time state of the codebase. Co-Authored-By: Claude <claude@anthropic.com>
2026-04-20 07:31:02 -04:00
These are injected by `scripts/patches/app-asar.sh` (inside `patch_app_asar`) and referenced in `package.json`'s `main` field. The wrapper pattern allows fixing Electron behavior without modifying the minified app code directly.
## Setting Up build-reference
If `build-reference/` is missing or you need to inspect source for a new version, follow these steps to download, extract, and beautify the source code.
### Prerequisites
```bash
# Install required tools
sudo apt install p7zip-full wget nodejs npm
# Install asar and prettier globally (or use npx)
npm install -g @electron/asar prettier
```
### Step 1: Download the Windows Installer
The Windows installer contains the app.asar which has the full Electron app source.
```bash
# Create working directory
mkdir -p build-reference && cd build-reference
# Download URL pattern (update version as needed):
# x64: https://downloads.claude.ai/releases/win32/x64/VERSION/Claude-COMMIT.exe
# arm64: https://downloads.claude.ai/releases/win32/arm64/VERSION/Claude-COMMIT.exe
# Example for version 1.1.381:
wget -O Claude-Setup-x64.exe "https://downloads.claude.ai/releases/win32/x64/1.1.381/Claude-c2a39e9c82f5a4d51f511f53f532afd276312731.exe"
```
### Step 2: Extract the Installer
```bash
# Extract the exe (it's a 7z archive)
7z x -y Claude-Setup-x64.exe -o"exe-contents"
# Find and extract the nupkg
cd exe-contents
NUPKG=$(find . -name "AnthropicClaude-*.nupkg" | head -1)
7z x -y "$NUPKG" -o"nupkg-contents"
cd ..
# Copy out the important files
cp exe-contents/nupkg-contents/lib/net45/resources/app.asar .
cp -a exe-contents/nupkg-contents/lib/net45/resources/app.asar.unpacked .
# Optional: copy tray icons for reference
mkdir -p tray-icons
cp exe-contents/nupkg-contents/lib/net45/resources/*.png tray-icons/ 2>/dev/null || true
cp exe-contents/nupkg-contents/lib/net45/resources/*.ico tray-icons/ 2>/dev/null || true
```
### Step 3: Extract app.asar
```bash
# Extract the asar archive
asar extract app.asar app-extracted
```
### Step 4: Beautify the JavaScript Files
The extracted JS files are minified. Use prettier to make them readable:
```bash
# Beautify all JS files in the build directory
npx prettier --write "app-extracted/.vite/build/*.js"
# Or beautify specific files
npx prettier --write app-extracted/.vite/build/index.js
npx prettier --write app-extracted/.vite/build/mainWindow.js
```
### Step 5: Clean Up (Optional)
```bash
# Remove intermediate files, keep only what's needed for reference
rm -rf exe-contents
rm Claude-Setup-x64.exe
rm -rf app.asar app.asar.unpacked # Keep only app-extracted
```
### Final Structure
```
build-reference/
├── app-extracted/
│ ├── .vite/
│ │ ├── build/
│ │ │ ├── index.js # Main process (beautified)
│ │ │ ├── mainWindow.js # Main window preload
│ │ │ ├── mainView.js # Main view preload
│ │ │ └── ...
│ │ └── renderer/
│ │ └── ...
│ ├── node_modules/
│ │ └── @ant/claude-native/ # Native bindings (stubs)
│ └── package.json
├── tray-icons/
│ ├── TrayIconTemplate.png # Black icon (for light panels)
│ ├── TrayIconTemplate-Dark.png # White icon (for dark panels)
│ └── ...
└── nupkg-contents/ # Optional: full extracted nupkg
```
## Adding New Package Formats or Repositories
When adding support for new distribution formats (e.g., RPM, Flatpak, Snap) or package repositories, follow these guidelines to avoid iterative debugging in CI.
### Research Before Implementing
1. **Understand the target system's constraints** - Each package format has specific rules:
- Version string formats (e.g., RPM cannot have hyphens in Version field)
- Required metadata fields
- Signing requirements and tools
2. **Search for existing CI implementations** - Look for "GitHub Actions [format] signing" or similar. Existing workflows reveal required flags, environment setup, and common pitfalls.
3. **Check tool behavior in non-interactive environments** - CI has no TTY. Tools like GPG need flags like `--batch` and `--yes` to work without prompts.
### Consider Concurrency
1. **Multiple jobs writing to the same branch will race** - If APT and DNF repos both push to `gh-pages`, add:
- Job dependencies (`needs: [other-job]`), or
- Retry loops with `git pull --rebase` before push
2. **External processes may also modify branches** - GitHub Pages deployment runs automatically and can cause push conflicts.
### Test the Full Pipeline
1. **Test CI steps locally first** - Run the signing/packaging commands manually to catch errors before committing.
2. **Use a test tag for new infrastructure** - Create a non-release tag to validate the full CI pipeline before merging to main.
3. **Verify the end-user experience** - After CI succeeds, actually test the install commands from the README on a clean system.
### Common CI Pitfalls
| Issue | Solution |
|-------|----------|
| GPG "cannot open /dev/tty" | Add `--batch` flag |
| GPG "File exists" error | Add `--yes` flag to overwrite |
| Push rejected (ref changed) | Add `git pull --rebase` before push, with retry loop |
| Version format invalid | Research target format's version constraints upfront |
| Signing key not found | Ensure key is imported before signing step, check key ID output |
## CI/CD
### Triggering Builds
```bash
# Trigger CI on a branch
gh workflow run CI --ref branch-name
# Watch the run
gh run watch RUN_ID
# Download artifacts
gh run download RUN_ID -n artifact-name
```
### Build Artifacts
- `claude-desktop-VERSION-amd64.deb` - Debian package for x86_64
- `claude-desktop-VERSION-amd64.AppImage` - AppImage for x86_64
- `claude-desktop-VERSION-arm64.deb` - Debian package for ARM64
- `claude-desktop-VERSION-arm64.AppImage` - AppImage for ARM64
- `result/` - Nix build output (symlink, gitignored)
## Distribution
APT and DNF binaries are fronted by a Cloudflare Worker at `pkg.claude-desktop-debian.dev`. Metadata (`InRelease`, `Packages`, `KEY.gpg`, `repodata/*`) passes through to the `gh-pages` branch; binary requests (`/pool/.../*.deb`, `/rpm/*/*.rpm`) get 302'd to the corresponding GitHub Release asset. This keeps `.deb` / `.rpm` files out of `gh-pages` entirely, so they never hit GitHub's 100 MB per-file push cap.
Key files:
- `worker/src/worker.js` — Worker source
- `worker/wrangler.toml` — Worker config (route, `custom_domain = true`)
- `.github/workflows/deploy-worker.yml` — deploys on push to `main` when `worker/**` changes
- `.github/workflows/apt-repo-heartbeat.yml` — daily chain validation, auto-opens tracking issue on failure
- `update-apt-repo` and `update-dnf-repo` jobs in `.github/workflows/ci.yml` — gate a strip step on Worker liveness, so binaries are removed from the local pool tree before push
Repo secrets: `CLOUDFLARE_API_TOKEN`, `CLOUDFLARE_ACCOUNT_ID`. Token scoped to the "Edit Cloudflare Workers" template.
Full details including the redirect chain, the http-scheme-downgrade gotcha, credential ownership, and heartbeat failure runbook: [`docs/learnings/apt-worker-architecture.md`](docs/learnings/apt-worker-architecture.md).
## Testing
### Local Build
```bash
./build.sh --build appimage --clean no
```
### Nix Build
```bash
nix build .#claude-desktop
nix build .#claude-desktop-fhs
```
### Testing AppImage
```bash
# Run with logging
./test-build/claude-desktop-*.AppImage 2>&1 | tee ~/.cache/claude-desktop-debian/launcher.log
```
## Debugging Workflow
### Inspecting the Running App's Code
```bash
# Find the mounted AppImage path
mount | grep claude
# Example: /tmp/.mount_claudeXXXXXX
# Extract the running app's asar for inspection
npx asar extract /tmp/.mount_claudeXXXXXX/usr/lib/node_modules/electron/dist/resources/app.asar /tmp/claude-inspect
# Search for patterns in the extracted code
grep -n "pattern" /tmp/claude-inspect/.vite/build/index.js
```
### Checking DBus/Tray Status
```bash
# List registered tray icons
gdbus call --session --dest=org.kde.StatusNotifierWatcher \
--object-path=/StatusNotifierWatcher \
--method=org.freedesktop.DBus.Properties.Get \
org.kde.StatusNotifierWatcher RegisteredStatusNotifierItems
# Find which process owns a DBus connection
gdbus call --session --dest=org.freedesktop.DBus \
--object-path=/org/freedesktop/DBus \
--method=org.freedesktop.DBus.GetConnectionUnixProcessID ":1.XXXX"
```
### Log Locations
- Launcher log: `~/.cache/claude-desktop-debian/launcher.log`
- App logs: `~/.config/Claude/logs/`
- Run with logging: `./app.AppImage 2>&1 | tee ~/.cache/claude-desktop-debian/launcher.log`
## Useful Locations
- App data: `~/.config/Claude/`
- Logs: `~/.config/Claude/logs/`
- SingletonLock: `~/.config/Claude/SingletonLock`
- Launcher log: `~/.cache/claude-desktop-debian/launcher.log`
## Versioning
Release versions are managed via two GitHub Actions repository variables (not files):
- **`REPO_VERSION`** - The project's own version (e.g., `1.3.23`). Bump this manually via `gh variable set REPO_VERSION --body "X.Y.Z"` when shipping project changes.
- **`CLAUDE_DESKTOP_VERSION`** - The upstream Claude Desktop version (e.g., `1.1.8629`). Updated automatically by the `check-claude-version` workflow when a new upstream release is detected.
### Tag format
Tags follow the pattern `v{REPO_VERSION}+claude{CLAUDE_DESKTOP_VERSION}`, e.g., `v1.3.23+claude1.1.7714`. Pushing a tag triggers the CI release build.
```bash
# Check current values
gh variable get REPO_VERSION
gh variable get CLAUDE_DESKTOP_VERSION
# Bump repo version and tag a release
gh variable set REPO_VERSION --body "1.3.24"
git tag "v1.3.24+claude$(gh variable get CLAUDE_DESKTOP_VERSION)"
git push origin "v1.3.24+claude$(gh variable get CLAUDE_DESKTOP_VERSION)"
```
docs: refresh for scripts/ split layout Updates agent definitions, learnings, CLAUDE.md, and BUILDING.md so path references point at the new module files instead of the old monolithic build.sh. Agent definitions: .claude/agents/issue-triage.md — table of per-category investigation paths now points at scripts/patches/*.sh and scripts/packaging/*.sh instead of "build.sh (search patch_X)". .claude/agents/electron-linux-specialist.md — patching-functions table now includes each function's file location; directory tree illustration reflects the new scripts/ layout. Documentation: CLAUDE.md — "Working with Minified JavaScript" section points at scripts/patches/*.sh; frame-fix injection attributed to scripts/patches/app-asar.sh; the version-bump checks now grep scripts/setup/detect-host.sh. docs/BUILDING.md — automated version detection paragraph now mentions scripts/setup/detect-host.sh as the file that holds the URLs. docs/learnings/cowork-vm-daemon.md — Patch 6 pointer now says scripts/patches/cowork.sh; line-number references dropped in favour of anchor-based search (line numbers drift between releases). docs/learnings/plugin-install.md — Key Files section points at scripts/patches/cowork.sh for patch_cowork_linux. Historical changelog-style references (e.g. docs/cowork-linux-handover.md describing what was "added to build.sh" during initial cowork work) are intentionally left unchanged — they describe a point-in-time state of the codebase. Co-Authored-By: Claude <claude@anthropic.com>
2026-04-20 07:31:02 -04:00
When upstream Claude Desktop updates, the `check-claude-version` workflow automatically updates `CLAUDE_DESKTOP_VERSION`, patches the URLs in `scripts/setup/detect-host.sh`, and creates a new tag — no manual intervention needed.
## Common Gotchas
- **`.zsync` files** - Used for delta updates, can be ignored/deleted
- **AppImage mount points** - Running AppImages mount to `/tmp/.mount_claude*`; check with `mount | grep claude`
- **Killing the app** - Must kill all electron child processes, not just the main one:
```bash
pkill -9 -f "mount_claude"
```
- **SingletonLock** - If app won't start, check for stale lock: `~/.config/Claude/SingletonLock`
- **Node version** - Build requires Node.js; the script downloads its own if needed
docs: refresh for scripts/ split layout Updates agent definitions, learnings, CLAUDE.md, and BUILDING.md so path references point at the new module files instead of the old monolithic build.sh. Agent definitions: .claude/agents/issue-triage.md — table of per-category investigation paths now points at scripts/patches/*.sh and scripts/packaging/*.sh instead of "build.sh (search patch_X)". .claude/agents/electron-linux-specialist.md — patching-functions table now includes each function's file location; directory tree illustration reflects the new scripts/ layout. Documentation: CLAUDE.md — "Working with Minified JavaScript" section points at scripts/patches/*.sh; frame-fix injection attributed to scripts/patches/app-asar.sh; the version-bump checks now grep scripts/setup/detect-host.sh. docs/BUILDING.md — automated version detection paragraph now mentions scripts/setup/detect-host.sh as the file that holds the URLs. docs/learnings/cowork-vm-daemon.md — Patch 6 pointer now says scripts/patches/cowork.sh; line-number references dropped in favour of anchor-based search (line numbers drift between releases). docs/learnings/plugin-install.md — Key Files section points at scripts/patches/cowork.sh for patch_cowork_linux. Historical changelog-style references (e.g. docs/cowork-linux-handover.md describing what was "added to build.sh" during initial cowork work) are intentionally left unchanged — they describe a point-in-time state of the codebase. Co-Authored-By: Claude <claude@anthropic.com>
2026-04-20 07:31:02 -04:00
- **Nix hashes** - When Claude Desktop version changes, both the URLs in `scripts/setup/detect-host.sh` and `nix/claude-desktop.nix` (version, URLs, SRI hashes) must be updated. The CI handles this automatically.
- **Claude Desktop version** - A GitHub Action automatically updates the `CLAUDE_DESKTOP_VERSION` repo variable and the URLs in `scripts/setup/detect-host.sh` on main when a new version is detected. Before committing `scripts/setup/detect-host.sh`, ensure your branch has the latest URLs:
```bash
# Check repo variable (source of truth)
gh variable get CLAUDE_DESKTOP_VERSION
docs: refresh for scripts/ split layout Updates agent definitions, learnings, CLAUDE.md, and BUILDING.md so path references point at the new module files instead of the old monolithic build.sh. Agent definitions: .claude/agents/issue-triage.md — table of per-category investigation paths now points at scripts/patches/*.sh and scripts/packaging/*.sh instead of "build.sh (search patch_X)". .claude/agents/electron-linux-specialist.md — patching-functions table now includes each function's file location; directory tree illustration reflects the new scripts/ layout. Documentation: CLAUDE.md — "Working with Minified JavaScript" section points at scripts/patches/*.sh; frame-fix injection attributed to scripts/patches/app-asar.sh; the version-bump checks now grep scripts/setup/detect-host.sh. docs/BUILDING.md — automated version detection paragraph now mentions scripts/setup/detect-host.sh as the file that holds the URLs. docs/learnings/cowork-vm-daemon.md — Patch 6 pointer now says scripts/patches/cowork.sh; line-number references dropped in favour of anchor-based search (line numbers drift between releases). docs/learnings/plugin-install.md — Key Files section points at scripts/patches/cowork.sh for patch_cowork_linux. Historical changelog-style references (e.g. docs/cowork-linux-handover.md describing what was "added to build.sh" during initial cowork work) are intentionally left unchanged — they describe a point-in-time state of the codebase. Co-Authored-By: Claude <claude@anthropic.com>
2026-04-20 07:31:02 -04:00
# Check current version in the detect_architecture case statement
grep -oP 'x64/\K[0-9]+\.[0-9]+\.[0-9]+' scripts/setup/detect-host.sh | head -1
# If outdated, pull URLs from main branch
docs: refresh for scripts/ split layout Updates agent definitions, learnings, CLAUDE.md, and BUILDING.md so path references point at the new module files instead of the old monolithic build.sh. Agent definitions: .claude/agents/issue-triage.md — table of per-category investigation paths now points at scripts/patches/*.sh and scripts/packaging/*.sh instead of "build.sh (search patch_X)". .claude/agents/electron-linux-specialist.md — patching-functions table now includes each function's file location; directory tree illustration reflects the new scripts/ layout. Documentation: CLAUDE.md — "Working with Minified JavaScript" section points at scripts/patches/*.sh; frame-fix injection attributed to scripts/patches/app-asar.sh; the version-bump checks now grep scripts/setup/detect-host.sh. docs/BUILDING.md — automated version detection paragraph now mentions scripts/setup/detect-host.sh as the file that holds the URLs. docs/learnings/cowork-vm-daemon.md — Patch 6 pointer now says scripts/patches/cowork.sh; line-number references dropped in favour of anchor-based search (line numbers drift between releases). docs/learnings/plugin-install.md — Key Files section points at scripts/patches/cowork.sh for patch_cowork_linux. Historical changelog-style references (e.g. docs/cowork-linux-handover.md describing what was "added to build.sh" during initial cowork work) are intentionally left unchanged — they describe a point-in-time state of the codebase. Co-Authored-By: Claude <claude@anthropic.com>
2026-04-20 07:31:02 -04:00
gh api repos/aaddrick/claude-desktop-debian/contents/scripts/setup/detect-host.sh?ref=main \
--jq '.content' | base64 -d | grep -E "claude_download_url="
```
Update both amd64 and arm64 URLs in `detect_architecture()` to match main