Skip to content

Latest commit

 

History

History
427 lines (301 loc) · 15.6 KB

File metadata and controls

427 lines (301 loc) · 15.6 KB

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/ 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 — NixOS packaging, Electron resource path resolution, testing without NixOS

Code Style

All shell scripts in this project must follow the Bash Style Guide. 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:

# 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

  1. Always use regex patterns when modifying the source JavaScript in build.sh. 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.

  4. Extract variable names dynamically rather than hardcoding them. Example from build.sh:

    # 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:

    # 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

These are injected by build.sh 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

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

# 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

# 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

# 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:

# 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)

# 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

# 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)

Testing

Local Build

./build.sh --build appimage --clean no

Nix Build

nix build .#claude-desktop
nix build .#claude-desktop-fhs

Testing AppImage

# 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

# 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

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

# 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)"

When upstream Claude Desktop updates, the check-claude-version workflow automatically updates CLAUDE_DESKTOP_VERSION, patches build.sh URLs, 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:
    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
  • Nix hashes - When Claude Desktop version changes, both build.sh URLs 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 build.sh on main when a new version is detected. Before committing build.sh, ensure your branch has the latest URLs:
    # Check repo variable (source of truth)
    gh variable get CLAUDE_DESKTOP_VERSION
    
    # Check current version in build.sh
    grep -oP 'x64/\K[0-9]+\.[0-9]+\.[0-9]+' build.sh | head -1
    
    # If outdated, pull URLs from main branch
    gh api repos/aaddrick/claude-desktop-debian/contents/build.sh?ref=main \
      --jq '.content' | base64 -d | grep -E "CLAUDE_DOWNLOAD_URL=|claude_download_url="
    Update both amd64 and arm64 URLs in detect_architecture() to match main