Skip to content

Commit f593ced

Browse files
authored
Merge pull request #443 from aaddrick/refactor/build-split-for-codeowners
refactor: split build.sh by subsystem + add CODEOWNERS
2 parents 2d6a645 + d939b07 commit f593ced

33 files changed

Lines changed: 3132 additions & 2839 deletions

.claude/agents/electron-linux-specialist.md

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ The project uses a three-layer interception pattern to fix Electron behavior on
7272

7373
```
7474
package.json (main: "frame-fix-entry.js")
75-
└── frame-fix-entry.js (generated by build.sh)
75+
└── frame-fix-entry.js (generated by scripts/patches/app-asar.sh)
7676
├── require('./frame-fix-wrapper.js') ← Intercepts require('electron')
7777
└── require('./<original-main>') ← Loads the real app
7878
```
@@ -94,29 +94,42 @@ package.json (main: "frame-fix-entry.js")
9494

9595
```
9696
claude-desktop-debian/
97-
├── build.sh # Main build script with all patches
97+
├── build.sh # Build orchestrator (sources scripts/patches/*.sh)
9898
├── scripts/
99-
│ ├── frame-fix-wrapper.js # BrowserWindow/Menu interceptor
99+
│ ├── _common.sh # Shared shell utilities
100+
│ ├── setup/ # Host detection, deps, download
101+
│ ├── patches/ # sed/regex patches on minified JS (per-subsystem)
102+
│ │ ├── _common.sh # extract_electron_variable, fix_native_theme_references
103+
│ │ ├── app-asar.sh # Asar repack, frame-fix wrapper injection
104+
│ │ ├── titlebar.sh
105+
│ │ ├── tray.sh # Tray menu handler + icon selection
106+
│ │ ├── quick-window.sh
107+
│ │ ├── claude-code.sh
108+
│ │ └── cowork.sh # Largest — cowork linux patching
109+
│ ├── staging/ # Post-patch file staging
110+
│ ├── packaging/ # deb/rpm/AppImage scripts
111+
│ ├── frame-fix-wrapper.js # BrowserWindow/Menu interceptor (copied in by patches/app-asar.sh)
100112
│ ├── claude-native-stub.js # Native module stubs for Linux
101-
│ └── launcher-common.sh # Wayland/X11 detection, Electron args
113+
│ └── launcher-common.sh # Wayland/X11 detection, Electron args
102114
├── .github/workflows/ # CI/CD pipelines
103115
└── resources/ # Desktop entries, icons
104-
# Note: frame-fix-entry.js is generated by build.sh at build time
116+
# Note: frame-fix-entry.js is generated by scripts/patches/app-asar.sh at build time
105117
```
106118

107-
### Patching Functions in build.sh
108-
109-
| Function | Purpose |
110-
|----------|---------|
111-
| `patch_app_asar()` | Orchestrates all patches: frame fix, titlebar, tray, theme, menu |
112-
| `patch_titlebar_detection()` | Removes `!` from `if(!isWindows && isMainWindow)` to enable titlebar |
113-
| `extract_electron_variable()` | Finds the minified variable name for `require("electron")` |
114-
| `fix_native_theme_references()` | Fixes wrong `*.nativeTheme` references to use the correct electron var |
115-
| `patch_tray_menu_handler()` | Makes tray rebuild async, adds mutex guard, DBus cleanup delay, startup skip |
116-
| `patch_tray_icon_selection()` | Switches from hardcoded template to theme-aware icon selection |
117-
| `patch_menu_bar_default()` | Changes `!!menuBarEnabled` to `menuBarEnabled !== false` |
118-
| `patch_quick_window()` | Adds `blur()` before `hide()` to fix submit issues |
119-
| `patch_linux_claude_code()` | Adds Linux platform detection for Claude Code binary |
119+
### Patching Functions (scripts/patches/*.sh)
120+
121+
| Function | File | Purpose |
122+
|----------|------|---------|
123+
| `patch_app_asar()` | `scripts/patches/app-asar.sh` | Extracts asar, injects frame-fix wrapper, repacks |
124+
| `patch_titlebar_detection()` | `scripts/patches/titlebar.sh` | Removes `!` from `if(!isWindows && isMainWindow)` to enable titlebar |
125+
| `extract_electron_variable()` | `scripts/patches/_common.sh` | Finds the minified variable name for `require("electron")` |
126+
| `fix_native_theme_references()` | `scripts/patches/_common.sh` | Fixes wrong `*.nativeTheme` references to use the correct electron var |
127+
| `patch_tray_menu_handler()` | `scripts/patches/tray.sh` | Makes tray rebuild async, adds mutex guard, DBus cleanup delay, startup skip |
128+
| `patch_tray_icon_selection()` | `scripts/patches/tray.sh` | Switches from hardcoded template to theme-aware icon selection |
129+
| `patch_menu_bar_default()` | `scripts/patches/tray.sh` | Changes `!!menuBarEnabled` to `menuBarEnabled !== false` |
130+
| `patch_quick_window()` | `scripts/patches/quick-window.sh` | Adds `blur()` before `hide()` to fix submit issues |
131+
| `patch_linux_claude_code()` | `scripts/patches/claude-code.sh` | Adds Linux platform detection for Claude Code binary |
132+
| `patch_cowork_linux()` | `scripts/patches/cowork.sh` | Cowork daemon auto-launch, VM lifecycle, sandbox wiring (largest patch set) |
120133

121134
### Environment Variables
122135

@@ -232,7 +245,7 @@ This agent provides Electron domain expertise; `cdd-code-simplifier` handles she
232245

233246
### Providing Guidance on Patches
234247

235-
When advising on new patches to minified JavaScript in `build.sh`:
248+
When advising on new patches to minified JavaScript (in `scripts/patches/*.sh`):
236249
1. Identify the Electron API or behavior being patched
237250
2. Explain the expected behavior on Linux vs Windows/macOS
238251
3. Suggest the regex pattern approach (dynamic extraction, whitespace handling)
@@ -245,7 +258,7 @@ When advising on new patches to minified JavaScript in `build.sh`:
245258

246259
When asked to analyze or fix an Electron/Linux integration issue:
247260

248-
1. **Identify the layer**: Is this a wrapper issue (frame-fix-wrapper.js), a build patch (build.sh sed patterns), a launcher issue (launcher-common.sh), or a native stub issue (claude-native-stub.js)?
261+
1. **Identify the layer**: Is this a wrapper issue (frame-fix-wrapper.js), a build patch (scripts/patches/*.sh sed patterns), a launcher issue (launcher-common.sh), or a native stub issue (claude-native-stub.js)?
249262

250263
2. **Check platform scope**: Does this affect all Linux, only Wayland, only X11, or specific desktop environments?
251264

.claude/agents/issue-triage.md

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ Use this when you're not confident enough to triage automatically. Examples: sec
4848
## INVESTIGATION RULES
4949

5050
### All bugs are ours to fix
51-
This project's goal is to take a working Anthropic product and make it work on Linux. Every bug is something we can investigate and potentially patch. Check `build.sh` patches first for bugs in patched areas (cowork, tray, frame, platform checks, window decorations). Read the relevant `patch_` function and trace what it modifies. If a behavior difference exists between the Windows/macOS app and our Linux build, that's a gap in our patching, not someone else's problem.
51+
This project's goal is to take a working Anthropic product and make it work on Linux. Every bug is something we can investigate and potentially patch. Check `scripts/patches/*.sh` first for bugs in patched areas (`cowork.sh`, `tray.sh`, `app-asar.sh`, `titlebar.sh`, `quick-window.sh`, `claude-code.sh`). Read the relevant `patch_` function and trace what it modifies. If a behavior difference exists between the Windows/macOS app and our Linux build, that's a gap in our patching, not someone else's problem.
5252

5353
### Verify before stating
5454
Only state facts you verified by reading actual code or running commands. Never claim code exists, functions behave a certain way, or patterns match without finding them in the source. If you cannot find evidence, say so explicitly rather than speculating.
@@ -66,7 +66,7 @@ If you cannot verify a root cause, classify as `needs-human` rather than constru
6666
These are specific mistakes that have caused bad triage outcomes:
6767

6868
- **Never claim code exists without grep evidence.** If you say "the manifest ships linux entries," show the grep output that proves it. (#329: triage claimed linux manifest entries existed when they don't)
69-
- **Never dismiss a bug as someone else's problem.** Every issue is ours to investigate. Check `build.sh` patches first since our patches are often the cause. (#329: triage blamed CDN when our checksum patch was wrong)
69+
- **Never dismiss a bug as someone else's problem.** Every issue is ours to investigate. Check `scripts/patches/*.sh` first since our patches are often the cause. (#329: triage blamed CDN when our checksum patch was wrong)
7070
- **Never speculate about network/CDN behavior.** Use `curl -sI URL | head -5` to check. Don't guess HTTP status codes.
7171
- **Never propose patches to code paths that aren't reached.** Trace the actual execution flow before suggesting a fix. (#329: triage suggested patching a catch block that was never hit)
7272
- **Never present a theory as a finding.** Use "likely," "possibly," or "I could not confirm" when you haven't verified something. Reserve declarative statements for verified facts.
@@ -79,15 +79,15 @@ When investigating bugs, search these files based on the issue category:
7979

8080
| Category | Files to check |
8181
|----------|---------------|
82-
| Build failures | `build.sh`, `.github/workflows/ci.yml`, `build-amd64.yml`, `build-arm64.yml` |
83-
| Window/frame issues | `frame-fix-wrapper.js`, `frame-fix-entry.js`, search reference source for `BrowserWindow` |
84-
| Tray icon issues | `build.sh` (search `patch_tray`), reference source for `Tray`, `StatusNotifier` |
85-
| Packaging (deb) | `build.sh` (search `build_deb`), `scripts/` directory |
86-
| Packaging (rpm) | `build.sh` (search `build_rpm`), `scripts/` directory |
87-
| Packaging (AppImage) | `build.sh` (search `build_appimage`) |
82+
| Build failures | `build.sh` (orchestrator), `scripts/setup/`, `.github/workflows/ci.yml`, `build-amd64.yml`, `build-arm64.yml` |
83+
| Window/frame issues | `scripts/frame-fix-wrapper.js`, `scripts/patches/titlebar.sh`, `scripts/patches/app-asar.sh`, reference source for `BrowserWindow` |
84+
| Tray icon issues | `scripts/patches/tray.sh`, reference source for `Tray`, `StatusNotifier` |
85+
| Packaging (deb) | `scripts/packaging/deb.sh`, `scripts/launcher-common.sh` |
86+
| Packaging (rpm) | `scripts/packaging/rpm.sh`, `scripts/launcher-common.sh` |
87+
| Packaging (AppImage) | `scripts/packaging/appimage.sh`, `scripts/launcher-common.sh` |
8888
| Packaging (nix) | `nix/` directory, `flake.nix` |
89-
| Cowork/MCP issues | `cowork-vm-service.js`, `build.sh` (search `patch_cowork`) |
90-
| Native module issues | `claude-native-stub.js`, `build.sh` (search `native`) |
89+
| Cowork/MCP issues | `scripts/cowork-vm-service.js`, `scripts/patches/cowork.sh`, `scripts/staging/cowork-resources.sh` |
90+
| Native module issues | `scripts/claude-native-stub.js`, `scripts/patches/cowork.sh` (node-pty install) |
9191
| CI/workflow issues | `.github/workflows/` directory |
9292

9393
The **reference source** (`/tmp/ref-source/app-extracted/`) contains the beautified Claude Desktop JavaScript. Use it to understand the original behavior that the build script patches or wraps. Key files:

.github/CODEOWNERS

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
# CODEOWNERS — per-subsystem review ownership
2+
#
3+
# Rules match top-to-bottom; the LAST matching rule wins.
4+
# Layout:
5+
# 1. Default owner
6+
# 2. Explicit @aaddrick assignments grouped by logical role
7+
# (listed even where redundant, so the intent is visible to
8+
# future collaborators scanning the file)
9+
# 3. Cowork and Nix overrides at the bottom so they stick
10+
#
11+
# Each listed user must be a repo collaborator (Settings →
12+
# Collaborators) with at least read access, or GitHub silently
13+
# ignores them.
14+
15+
# ---- Default: aaddrick owns anything not explicitly claimed ----
16+
* @aaddrick
17+
18+
# ---- Build orchestration ----
19+
# The top-level dispatcher and shared shell utilities.
20+
/build.sh @aaddrick
21+
/scripts/_common.sh @aaddrick
22+
23+
# ---- Setup (host detection, dependencies, upstream download) ----
24+
/scripts/setup/ @aaddrick
25+
26+
# ---- Electron patches / minified JS ----
27+
# The regex-driven patches applied to the unpacked app.asar, plus
28+
# the frame-fix wrapper and native-binding stubs that ride along.
29+
/scripts/patches/_common.sh @aaddrick
30+
/scripts/patches/app-asar.sh @aaddrick
31+
/scripts/patches/titlebar.sh @aaddrick
32+
/scripts/patches/claude-code.sh @aaddrick
33+
/scripts/frame-fix-wrapper.js @aaddrick
34+
/scripts/claude-native-stub.js @aaddrick
35+
36+
# ---- Linux desktop integration ----
37+
# Tray, menu bar, and quick-window behavior on Wayland/X11.
38+
/scripts/patches/tray.sh @aaddrick
39+
/scripts/patches/quick-window.sh @aaddrick
40+
41+
# ---- Staging (non-cowork) ----
42+
# Electron copy-out, icon processing, locales, SSH helpers.
43+
/scripts/staging/electron.sh @aaddrick
44+
/scripts/staging/icons.sh @aaddrick
45+
/scripts/staging/locales.sh @aaddrick
46+
/scripts/staging/ssh-helpers.sh @aaddrick
47+
48+
# ---- Packaging formats (deb, rpm, AppImage) + runtime launcher ----
49+
/scripts/packaging/ @aaddrick
50+
/scripts/launcher-common.sh @aaddrick
51+
52+
# ---- Distribution & signing ----
53+
# APT/DNF repo publishing, GPG signing, release automation.
54+
# Most of this lives in workflows — gh-pages branch content isn't
55+
# reachable via CODEOWNERS.
56+
/.github/workflows/ @aaddrick
57+
/scripts/resolve-download-url.py @aaddrick
58+
59+
# ---- CI / other GitHub metadata ----
60+
/.github/ @aaddrick
61+
62+
# ---- Docs & style ----
63+
/README.md @aaddrick
64+
/CLAUDE.md @aaddrick
65+
/STYLEGUIDE.md @aaddrick
66+
/docs/ @aaddrick
67+
68+
#===============================================================================
69+
# Overrides — listed last so their assignments stick against the
70+
# broad globs above (/docs/, /.github/, etc.)
71+
#===============================================================================
72+
73+
# ---- Cowork ----
74+
# Electron-side patching, staging, daemon, and integration tests.
75+
/scripts/patches/cowork.sh @RayCharlizard
76+
/scripts/staging/cowork-resources.sh @RayCharlizard
77+
/scripts/cowork-vm-service.js @RayCharlizard
78+
/tests/cowork-*.bats @RayCharlizard
79+
/docs/cowork-*.md @RayCharlizard
80+
81+
# ---- Nix ----
82+
/flake.nix @typedrat
83+
/flake.lock @typedrat
84+
/nix/ @typedrat

.github/workflows/check-claude-version.yml

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -68,13 +68,13 @@ jobs:
6868
echo "arm64_url=$ARM64_URL" >> $GITHUB_OUTPUT
6969
echo "claude_version=$CLAUDE_VERSION" >> $GITHUB_OUTPUT
7070
71-
- name: Get current URLs from build.sh
71+
- name: Get current URLs from scripts/setup/detect-host.sh
7272
id: current_urls
7373
run: |
74-
# Extract current URLs from build.sh
75-
# The build.sh case statement uses x86_64/aarch64 patterns with claude_download_url on the next line
76-
CURRENT_AMD64_URL=$(grep -E "x86_64\)" -A1 build.sh | grep -oP "claude_download_url='\\K[^']+")
77-
CURRENT_ARM64_URL=$(grep -E "aarch64\)" -A1 build.sh | grep -oP "claude_download_url='\\K[^']+")
74+
# Extract current URLs from scripts/setup/detect-host.sh
75+
# The scripts/setup/detect-host.sh case statement uses x86_64/aarch64 patterns with claude_download_url on the next line
76+
CURRENT_AMD64_URL=$(grep -E "x86_64\)" -A1 scripts/setup/detect-host.sh | grep -oP "claude_download_url='\\K[^']+")
77+
CURRENT_ARM64_URL=$(grep -E "aarch64\)" -A1 scripts/setup/detect-host.sh | grep -oP "claude_download_url='\\K[^']+")
7878
7979
echo "Current AMD64 URL: $CURRENT_AMD64_URL"
8080
echo "Current ARM64 URL: $CURRENT_ARM64_URL"
@@ -132,35 +132,35 @@ jobs:
132132
echo "update_needed=false" >> $GITHUB_OUTPUT
133133
fi
134134
135-
- name: Update build.sh with new URLs
135+
- name: Update scripts/setup/detect-host.sh with new URLs
136136
if: steps.check_update.outputs.update_needed == 'true'
137137
run: |
138138
NEW_AMD64_URL="${{ steps.resolve_urls.outputs.amd64_url }}"
139139
NEW_ARM64_URL="${{ steps.resolve_urls.outputs.arm64_url }}"
140140
CURRENT_AMD64_URL="${{ steps.current_urls.outputs.current_amd64_url }}"
141141
CURRENT_ARM64_URL="${{ steps.current_urls.outputs.current_arm64_url }}"
142142
143-
echo "Updating build.sh with new URLs..."
143+
echo "Updating scripts/setup/detect-host.sh with new URLs..."
144144
145145
# Update AMD64 URL
146146
if [ -n "$NEW_AMD64_URL" ] && [ "$NEW_AMD64_URL" != "$CURRENT_AMD64_URL" ]; then
147147
echo "Updating AMD64 URL..."
148148
# Escape special characters for sed
149149
ESCAPED_CURRENT=$(printf '%s\n' "$CURRENT_AMD64_URL" | sed 's/[[\.*^$()+?{|]/\\&/g')
150150
ESCAPED_NEW=$(printf '%s\n' "$NEW_AMD64_URL" | sed 's/[&/\]/\\&/g')
151-
sed -i "s|$ESCAPED_CURRENT|$ESCAPED_NEW|g" build.sh
151+
sed -i "s|$ESCAPED_CURRENT|$ESCAPED_NEW|g" scripts/setup/detect-host.sh
152152
fi
153153
154154
# Update ARM64 URL (if we have a new one)
155155
if [ -n "$NEW_ARM64_URL" ] && [ "$NEW_ARM64_URL" != "$CURRENT_ARM64_URL" ]; then
156156
echo "Updating ARM64 URL..."
157157
ESCAPED_CURRENT=$(printf '%s\n' "$CURRENT_ARM64_URL" | sed 's/[[\.*^$()+?{|]/\\&/g')
158158
ESCAPED_NEW=$(printf '%s\n' "$NEW_ARM64_URL" | sed 's/[&/\]/\\&/g')
159-
sed -i "s|$ESCAPED_CURRENT|$ESCAPED_NEW|g" build.sh
159+
sed -i "s|$ESCAPED_CURRENT|$ESCAPED_NEW|g" scripts/setup/detect-host.sh
160160
fi
161161
162-
echo "Updated build.sh URLs:"
163-
grep "claude_download_url=" build.sh
162+
echo "Updated scripts/setup/detect-host.sh URLs:"
163+
grep "claude_download_url=" scripts/setup/detect-host.sh
164164
165165
- name: Compute SRI hashes for Nix
166166
if: steps.check_update.outputs.update_needed == 'true'
@@ -189,30 +189,30 @@ jobs:
189189
echo "arm64_sha256=$ARM64_HEX" >> $GITHUB_OUTPUT
190190
fi
191191
192-
- name: Update build.sh SHA-256 checksums
192+
- name: Update scripts/setup/detect-host.sh SHA-256 checksums
193193
if: steps.check_update.outputs.update_needed == 'true'
194194
run: |
195195
AMD64_SHA256="${{ steps.nix_hashes.outputs.amd64_sha256 }}"
196196
ARM64_SHA256="${{ steps.nix_hashes.outputs.arm64_sha256 }}"
197197
198-
echo "Updating build.sh SHA-256 checksums..."
198+
echo "Updating scripts/setup/detect-host.sh SHA-256 checksums..."
199199
200200
# Update AMD64 hash (in x86_64 case block)
201201
if [ -n "$AMD64_SHA256" ]; then
202202
sed -i "/x86_64)/,/;;/{
203203
s/claude_exe_sha256='[^']*'/claude_exe_sha256='$AMD64_SHA256'/
204-
}" build.sh
204+
}" scripts/setup/detect-host.sh
205205
fi
206206
207207
# Update ARM64 hash (in aarch64 case block)
208208
if [ -n "$ARM64_SHA256" ]; then
209209
sed -i "/aarch64)/,/;;/{
210210
s/claude_exe_sha256='[^']*'/claude_exe_sha256='$ARM64_SHA256'/
211-
}" build.sh
211+
}" scripts/setup/detect-host.sh
212212
fi
213213
214-
echo "Updated build.sh checksums:"
215-
grep "claude_exe_sha256=" build.sh
214+
echo "Updated scripts/setup/detect-host.sh checksums:"
215+
grep "claude_exe_sha256=" scripts/setup/detect-host.sh
216216
217217
# VM bundle checksums removed — Patch 4 now injects empty linux
218218
# file arrays since the VM backend is non-functional on Linux.
@@ -268,10 +268,10 @@ jobs:
268268
git config user.email "github-actions[bot]@users.noreply.github.com"
269269
270270
# Check if there are changes to commit
271-
if git diff --quiet build.sh nix/claude-desktop.nix; then
272-
echo "No changes to build.sh or nix/claude-desktop.nix"
271+
if git diff --quiet scripts/setup/detect-host.sh nix/claude-desktop.nix; then
272+
echo "No changes to scripts/setup/detect-host.sh or nix/claude-desktop.nix"
273273
else
274-
git add build.sh nix/claude-desktop.nix
274+
git add scripts/setup/detect-host.sh nix/claude-desktop.nix
275275
git commit -m "$(cat <<COMMIT_MSG
276276
Update Claude Desktop download URLs to version $CLAUDE_VERSION
277277

0 commit comments

Comments
 (0)