144 Commits

Author SHA1 Message Date
Aaddrick
5c8191e82f 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
aaddrick
ff4821e087 refactor: split build.sh into topical modules under scripts/
Splits the 2124-line build.sh into a 318-line orchestrator plus
16 topical modules, grouped so CODEOWNERS can assign per-subsystem
reviewers:

    scripts/_common.sh              shared shell utilities
    scripts/setup/                  host detection, deps, download
    scripts/patches/                regex patches on minified JS
      _common.sh                    extract_electron_variable etc.
      app-asar.sh                   wrapper injection
      titlebar.sh
      tray.sh                       menu handler + icon selection
      quick-window.sh
      claude-code.sh
      cowork.sh                     cowork linux patching (largest)
    scripts/staging/                post-patch file staging

build.sh now sources each module in dependency order and retains
only run_packaging, cleanup_build, print_next_steps, and main.
All globals stay at the top of build.sh and are read by sourced
modules; each module's header documents which globals it reads and
mutates (implicit-contract documentation).

This is a pure-move refactor. Function bodies were copied verbatim
— verified by byte-identical diff of the function set vs the
pre-split build.sh (34 functions, all present with identical bodies).

Note: .github/workflows/shellcheck.yml may benefit from a '-x' flag
so shellcheck follows the new '# shellcheck source=' directives, but
that CI tweak is left as a separate concern.

Co-Authored-By: Claude <claude@anthropic.com>
2026-04-20 07:12:22 -04:00
Travis
44cd5a6c24 fix: forward userSelectedFolders[0] as sharedCwdPath on cowork spawn (#412) (#436)
* fix: forward userSelectedFolders[0] as sharedCwdPath on cowork spawn (#412)

The cowork-vm-service daemon already honors a `sharedCwdPath` field on
the spawn IPC payload with priority over `cwd` (resolveWorkDir in
scripts/cowork-vm-service.js:500), but the upstream Electron app never
populates it on Linux backends. Every spawn arrives with only
`cwd=/sessions/{name}`, so the daemon derives the host path from
mountMap heuristics (PRs #389/#392/#411 cover the symptoms).

Patch 12 threads the user-selected folder through three sites so the
daemon receives the host path explicitly:

  12a. At `this.getVMSpawnFunction({...})` config assembly, inject
       `sharedCwdPath: SESSION.userSelectedFolders?.[0]` alongside the
       existing mount config.
  12b. At the Kyr() -> VMClient.spawn() call, forward
       `SESSION.sharedCwdPath` as a new 13th positional argument.
  12c. In the spawn() method body, accept a new trailing parameter
       and set it on the IPC payload with a `VAR && (I.sharedCwdPath=VAR)`
       guard matching the existing setter chain.

All three sub-patches detect prior application and no-op on re-run
(idempotent). If any site fails to match on a future upstream, the
daemon-side fallback from #392 keeps cwd resolution working — the
daemon workarounds in #389/#392/#411 remain as safety nets.

Verified against app.asar extracted from Claude-Setup-x64.exe for
version 1.3109.0. All three edits apply, output parses cleanly, a
second run is a no-op.

Co-Authored-By: Claude <claude@anthropic.com>

* refactor: simplify Patch 12 block

Reduce Patch 12 (#412) by 25 lines without changing the patched
output:

- 12a: use extractBlock('{') directly on the getVMSpawnFunction
  argument and splice before its closing '}', removing the
  inverted backward walk over the parenthesised block.
- 12a: replace the exec-until-null loop with matchAll + last
  element to read the session-var name.
- 12c: replace the whole.replace().replace() + code.replace(whole)
  reassembly chain with an index-based splice using
  spawnMatch.index.
- 12c: drop the unneeded .split('') on the letter bag and inline
  newArgList.
- Trim the prologue comment to match the density of Patches 1-10.

Patched index.js is byte-identical against the 1.3109.0 fixture
and the three idempotency log lines still fire on re-run.

Co-Authored-By: Claude <claude@anthropic.com>

* fix: address aaddrick's review — robust 12a anchor, 12b uniqueness assertion

Two fixes from PR #436 review:

1. **12a: drop fragile backscan, route through this.sessions.get().**
   The previous `VAR.userSelectedFolders` backscan returned 10 matches
   across 4 distinct vars (t, se, Ke, We) in the v1.3109.0 window —
   last-match landed on `t` by coincidence, and `We` in particular is
   a for-loop variable one upstream re-order away from becoming the
   new "last". Swap to the canonical accessor the class already uses:
   `this.sessions.get(sessionId)?.userSelectedFolders?.[0]`. The
   sessionId var is extracted from the config's first field
   `{sessionId:VAR` — scoped to the config block, 100% guaranteed
   present, immune to unrelated references leaking in.

2. **12b: matchAll + uniqueness assertion.** The previous code used
   `code.match()` which silently took the first hit if a second
   upstream call site ever appeared. Switch to `matchAll` with
   `length === 1` assertion; WARN-and-skip on anything else so a
   wrong-site forwarding becomes detectable instead of silent.

3. **Drop misleading ordering comment in 12c.** The "12c before 12b
   so property name is fixed" note was wrong — the property name is
   a hardcoded string literal in both sub-patches, so the ordering
   is cosmetic.

Verified: dry-run still applies all three patches on 1.3109.0 source,
output passes `node --check`, the three sharedCwdPath edits are
byte-stable across runs (the non-idempotency in Patch 9 is
pre-existing and orthogonal).

Co-Authored-By: Claude <claude@anthropic.com>

---------

Co-authored-by: Claude <claude@anthropic.com>
2026-04-19 12:25:29 -04:00
Aaddrick
e92aea149f fix: strip mode on node-pty cp at source, retire chmod (#438)
Follow-up to #432. Instead of chmod'ing read-only files after the fact
in finalize_app_asar(), pass --no-preserve=mode on the cp invocations
in install_node_pty() so Nix-store 0444 bits never propagate into the
staging tree. This makes app.asar.contents internally consistent and
removes the need for the post-hoc chmod.

Also applied to the finalize_app_asar() cp from $pty_release_dir for
consistency, since that read also originates in the Nix store when
--node-pty-dir is set.

npm-install flows are unaffected: --no-preserve=mode forces default
(0666 & ~umask) mode, which matches what npm-installed files already
have.

Co-authored-by: Claude <claude@anthropic.com>
2026-04-19 08:10:22 -04:00
Alexis Williams
50b10ed953 fix: chmod node-pty unpacked files before overwriting in Nix builds (#432)
asar pack --unpack preserves Nix store read-only permissions on .node
files it extracts to app.asar.unpacked/. The subsequent cp -r fails
with 'Permission denied' trying to overwrite those read-only files.

Add chmod -R u+w before the copy to make any existing files writable.
2026-04-19 08:01:15 -04:00
Travis
9e577cc3d5 fix: suppress Cowork tab auto-select on every launch (#341) (#433)
* fix: suppress Cowork tab auto-select on every launch (#341)

Patch 4's empty Linux bundle manifest makes `[].every()` return
true vacuously, so `iBA()` reports "VM files present" and
`getDownloadStatus()` returns Ready on every startup. The remote
web app treats a startup observation of Ready as the same
download-completed transition that auto-navigates macOS/Windows
users to Cowork after their first download — Linux users hit it
on every launch.

Add Patch 4b to short-circuit `getDownloadStatus()` to
NotDownloaded on Linux. `iBA()` is left alone so the `download()`
IPC still succeeds instantly and the Cowork tab still works when
clicked — the web app's setup UI just passes through.

Anchor is stable: `getDownloadStatus` and the enum property
names (.Downloading, .Ready, .NotDownloaded) are readable in the
minified bundle. Verified against 1.3109.0 with an isolated
node run; idempotent on re-runs.

Co-Authored-By: Claude <claude@anthropic.com>

* refactor: destructure regex match in Patch 4b

Co-Authored-By: Claude <claude@anthropic.com>

---------

Co-authored-by: Claude <claude@anthropic.com>
2026-04-19 00:02:58 -05:00
Travis Stockton
87f4f0fca7 Merge main to revalidate Patch 6 against 1.3109.0
Catches up to current upstream URLs (1.3109.0), Joost-Maker's #418
identifier-widening fix in Patch 9, and #421's existsync/node-pty fix.
PR #410's last CI ran on April 16 against 1.2773.0 and showed
'WARNING: Could not find retry delay for auto-launch patch' — this
merge re-runs CI against current main to surface whether Patch 6's
regex anchors still match on 1.3109.0.
2026-04-18 21:16:48 -05:00
Joost-Maker
3150477f55 fix: mark node-pty native modules as unpacked in asar manifest
`install_node_pty()` copied only `lib/` and `package.json` into
`app.asar.contents/node_modules/node-pty/`, and `finalize_app_asar()`
packed app.asar without `--unpack`. The `.node` binaries were then
separately dropped into `app.asar.unpacked/.../node-pty/build/Release/`.

Result: the asar manifest had no entry for `node-pty/build/` at all.
When node-pty's loader (inside the asar) does
`require('../build/Release/pty.node')` from `lib/utils.js`, Electron's
asar -> .unpacked redirect never fires because the redirect requires
a manifest entry annotated as unpacked. The require returns
MODULE_NOT_FOUND despite the binary existing on disk, and Claude Code
mode shows "Failed to load terminal backend" on every shell session
attempt.

Two-part fix:
1. install_node_pty(): also stage `$pty_src_dir/build/` into
   app.asar.contents so the pack step has the .node files to work
   with.
2. finalize_app_asar(): pass `--unpack '**/*.node'` to `asar pack`
   so the binaries get moved into app.asar.unpacked/ AND recorded
   in the manifest as unpacked.

Verified: the new asar manifest now includes
  UNPACKED: /node_modules/node-pty/build/Release/pty.node
  UNPACKED: /node_modules/node-pty/build/Release/conpty.node
  UNPACKED: /node_modules/node-pty/build/Release/conpty_console_list.node
and Claude Code's terminal loads successfully.

The pre-existing copy-to-.unpacked step in finalize_app_asar() is now
redundant but harmless (writes the same bytes); kept for now to
minimize diff and preserve the --node-pty-dir flow.

Co-Authored-By: Claude <claude@anthropic.com>
2026-04-17 14:05:40 +02:00
Joost-Maker
2f6194ff5a fix: capture $-prefixed identifiers when extracting cowork vars
Patch 9 in patch_cowork_linux() extracts six minified variable names
from the win32 block to template a Linux block. The extraction regexes
used `(\w+)` which does not match `$` — JavaScript identifiers can
start with `$`, `_`, or a letter.

Claude >= 1.3109.0 renamed the local fs reference inside startVM's
win32 block from `e` to `$e` (likely to avoid shadowing the function
parameter `e`, which is the options object). The existing regex
`(\w+)\.existsSync\(` scans `$e.existsSync(U)`, skips the `$`, and
captures just `e`. Patch 9 then injects a Linux block calling
`e.existsSync(_ls)` — but `e` resolves at runtime to the options
object, so the call dies with `TypeError: e.existsSync is not a
function` and Cowork never boots on Linux.

Widen all six extraction patterns to `[$\w]+`. Also widen the
adjacent unanchored matchers in `archMatch` for consistency.

Add a defensive strip step before injection: if a future upstream
emits its own `if(process.platform==="linux"){...}` block right after
the win32 close brace, brace-count to its end and remove it so we
don't end up with two competing Linux blocks.

Verified: a clean rebuild now logs
  vars: path=ae fs=$e log=qe stream=SL arch=Bre bundle=r
and the asar's injected block contains `$e.existsSync(_ls)`. Cowork
starts cleanly: `[VM:start] Startup complete, total time: 1242ms`,
the VM agent spawns, and prompts get responses end-to-end.

Fixes #418

Co-Authored-By: Claude <claude@anthropic.com>
2026-04-17 14:04:01 +02:00
github-actions[bot]
20802908a7 Update Claude Desktop download URLs to version 1.3109.0
Updated download URLs resolved from official redirect endpoints.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-04-17 01:39:24 +00:00
Travis Stockton
ef2aac500d refactor: simplify cowork daemon recovery patch (#408)
Collapse Patch 6b console.log calls to single lines to match the
convention used in Patches 7-9. Each message fits well under 80
characters and doesn't need to be split across three lines.

Co-Authored-By: Claude <claude@anthropic.com>
2026-04-16 16:53:30 -05:00
Travis Stockton
cb0d636f20 fix: restore cowork-vm-service daemon recovery after crash (#408)
Two coordinated patches in build.sh's patch_cowork_linux function
address the daemon's inability to recover after mid-session death.

Patch 6 (reworked):
- Replace the one-shot _svcLaunched boolean with a timestamp-based
  _lastSpawn cooldown (10s). The retry loop can now re-fork the
  daemon on subsequent iterations after a crash instead of seeing
  the boolean already set and skipping the spawn forever.
- Redirect the forked daemon's stdout and stderr to
  ~/.config/Claude/logs/cowork_vm_daemon.log so node-level crash
  output is no longer lost to stdio:"ignore". Falls back cleanly
  if the log dir can't be opened.

Patch 6b (new):
- Extend the auto-reinstall delete list to also wipe
  sessiondata.img and rootfs.img.zst. Upstream preserves these to
  avoid re-download, but on 1.2773.0 the preserved files put the
  daemon into an unstartable state that persists across app
  restart and OS reboot (confirmed by issue reporter). Trade-off:
  next successful startup re-extracts these images; acceptable
  because auto-reinstall only runs after startup already failed.

Co-Authored-By: Claude <claude@anthropic.com>
2026-04-16 12:06:07 -05:00
github-actions[bot]
214d5e92d4 Update Claude Desktop download URLs to version 1.2773.0
Updated download URLs resolved from official redirect endpoints.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-04-16 01:39:35 +00:00
Aaddrick
ab3396043f fix: gate quick window patch to KDE sessions only (#393) (#406)
* fix: gate quick window patch to KDE sessions only (#393)

PR #390 fixed a quick-window regression on KDE but regressed GNOME/Ubuntu —
@Andrej730 confirmed removing patch_quick_window restores quick entry on
Ubuntu 24.04. Without a reproduction environment for GNOME yet, the safe
minimum-viable fix is to gate the patch behind a runtime XDG_CURRENT_DESKTOP
check: apply on KDE (where the fix is validated), fall back to upstream
behavior everywhere else (which Ubuntu users confirmed works).

Both halves of the patch are gated:

- blur() before hide(): wrapped in a ternary so non-KDE sessions get the
  original unconditional hide()
- focusFn()||show() replacement: wrapped so non-KDE sessions keep the
  original focus check instead of the visibility check

Adds an idempotency pre-check in the node block (XDG_CURRENT_DESKTOP
substring near the anchor) so re-runs skip cleanly. Part 1's existing
grep idempotency still works because `Q.blur(),Q.hide()` appears inside
the ternary literally.

This is a temporary gate. VMs are being spun up to bisect which half
actually regresses GNOME; once isolated, only that half needs the gate.

Refs #393, #370, #404

Co-Authored-By: Claude <claude@anthropic.com>

* style: split de_check assignment to fit under 80 chars

Matches the concatenation style already used for the node block's
deCheck, bringing the bash literal under the style guide's line limit.
No functional change — the expanded string is identical.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 18:23:27 -04:00
github-actions[bot]
158d43544c Update Claude Desktop download URLs to version 1.2581.0
Updated download URLs resolved from official redirect endpoints.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-04-15 01:39:29 +00:00
github-actions[bot]
e5cc4b21f8 Update Claude Desktop download URLs to version 1.2278.0
Updated download URLs resolved from official redirect endpoints.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-04-14 01:39:13 +00:00
Aaddrick
605ccab0c9 fix: kill cowork daemon on app quit (#391)
* fix: kill cowork daemon on app quit

The upstream cowork-vm-shutdown quit handler uses the Swift VM addon
which isn't available on Linux, so it's never registered. Our forked
cowork-vm-service daemon was invisible to the quit system, surviving
app exit and leaving QEMU/virtiofsd processes running.

Register a Linux-specific quit handler via the upstream
registerQuitHandler infrastructure. The handler sends SIGTERM to the
daemon (which already handles it gracefully), verifies the PID via
/proc/cmdline to prevent killing the wrong process on PID reuse, and
polls for exit up to 10 seconds.

The daemon PID is captured at fork time on a global, avoiding any
need for pgrep/execSync at quit time. The handler is registered
unconditionally for Linux so it works regardless of how the daemon
was launched.

Fixes #369

Co-Authored-By: Claude <claude@anthropic.com>

* style: simplify quit handler patch comments and scope

Add block scope for consistency with Patches 8-9, trim header comment,
remove hardcoded minified name from implementation comment, simplify
insertIdx calculation to match Patch 4 pattern.

Co-Authored-By: Claude <claude@anthropic.com>

---------

Co-authored-by: Claude <claude@anthropic.com>
2026-04-12 19:45:54 -04:00
Aaddrick
32660beed2 fix: rewrite quick window patch with dynamic symbol extraction (#390)
* fix: rewrite quick window patch with dynamic symbol extraction

The original patch from PR #147 hardcoded the minified variable name
`e` (e.g. `s/e.hide()/e.blur(),e.hide()/`), which stopped matching
after upstream minifier changes renamed the variable. This silently
regressed the fix for #144 (quick entry submit not showing main window).

Replace with two robust patches:

1. Extract the quick window variable dynamically via the unique
   `setAlwaysOnTop(!0,"pop-up-menu")` anchor, then inject `blur()`
   before `hide()` with correct operator precedence (wrapped in parens
   to preserve the short-circuit guard).

2. Fix the main window not appearing after quick entry submit. The
   upstream code gates `Lt.show()` on a focus check (`isFocused()`),
   but on Linux `webContents.isFocused()` can return stale true for
   hidden windows. Replace with the visibility check (`isVisible()`)
   that other show-window paths in the same codebase already use.
   Implemented as a Node.js inline patch anchored on unique
   "[QuickEntry]" log strings, consistent with the cowork patches.

Fixes #144

Co-Authored-By: Claude <claude@anthropic.com>

* style: simplify comments in patch_quick_window

Remove version-specific minified names from comments (they change
between releases) and condense redundant explanations.

Co-Authored-By: Claude <claude@anthropic.com>

---------

Co-authored-by: Claude <claude@anthropic.com>
2026-04-12 17:59:21 -04:00
github-actions[bot]
218934d14d Update Claude Desktop download URLs to version 1.1617.0
Updated download URLs resolved from official redirect endpoints.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-04-10 01:40:40 +00:00
github-actions[bot]
814cd524c0 Update Claude Desktop download URLs to version 1.1348.0
Updated download URLs resolved from official redirect endpoints.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-04-09 01:40:07 +00:00
github-actions[bot]
b1e1ea8e78 Update Claude Desktop download URLs to version 1.1062.0
Updated download URLs resolved from official redirect endpoints.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-04-08 01:39:02 +00:00
github-actions[bot]
a918cd8091 Update Claude Desktop download URLs to version 1.569.0
Updated download URLs resolved from official redirect endpoints.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-04-03 01:38:50 +00:00
github-actions[bot]
a5bffe62c9 Update Claude Desktop download URLs to version 1.2.234
Updated download URLs resolved from official redirect endpoints.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-04-02 01:39:14 +00:00
github-actions[bot]
a855b484ab Update Claude Desktop download URLs to version 1.1.9669
Updated download URLs resolved from official redirect endpoints.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-03-31 01:38:42 +00:00
github-actions[bot]
036e35dc0f Update Claude Desktop download URLs to version 1.1.9493
Updated download URLs resolved from official redirect endpoints.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-03-30 01:39:15 +00:00
github-actions[bot]
02b183df2c Update Claude Desktop download URLs to version 1.1.9310
Updated download URLs resolved from official redirect endpoints.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-03-28 01:38:56 +00:00
github-actions[bot]
146e40731a Update Claude Desktop download URLs to version 1.1.9134
Updated download URLs resolved from official redirect endpoints.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-03-27 01:39:03 +00:00
Aaddrick
9afacd57e2 fix: extract minified vars dynamically in cowork patch 9 (#345)
* fix: extract minified vars dynamically in cowork patch 9 (#344)

Patch 9 (smol-bin VHDX copy) hardcoded minified variable names
(Qe, ft, vg, tt, uX) which change between upstream releases,
causing "Qe is not defined" crashes at runtime.

Extract all 6 variables dynamically from the nearby win32 block
using regex patterns that handle both minified and beautified code.
Add diagnostic logging of extracted variable names.

Also document the repo versioning system (REPO_VERSION,
CLAUDE_DESKTOP_VERSION variables and tag format) in CLAUDE.md.

Fixes #344

Co-Authored-By: Claude <claude@anthropic.com>

* style: simplify console.log calls in cowork patch 9

Remove redundant comment restating the regex pattern, and replace
unnecessarily split string concatenations in console.log calls
with template literals (consistent with the existing pattern on
the final patchCount summary line).

Co-Authored-By: Claude <claude@anthropic.com>

---------

Co-authored-by: Claude <claude@anthropic.com>
2026-03-25 06:25:13 -04:00
github-actions[bot]
0a61b73a3a Update Claude Desktop download URLs to version 1.1.8629
Updated download URLs resolved from official redirect endpoints.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-03-25 01:38:22 +00:00
github-actions[bot]
18591bd301 Update Claude Desktop download URLs to version 1.1.8359
Updated download URLs resolved from official redirect endpoints.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-03-24 01:38:26 +00:00
Aaddrick
a3190c38b9 fix: disable VM file downloads on Linux to prevent checksum loop (#337)
* fix: disable VM file downloads on Linux to prevent checksum loop (#334)

Patch 4 in patch_cowork_linux() previously copied win32 VM file entries
(rootfs.vhdx, vmlinuz, initrd) with Linux-specific checksums. These
checksums drifted from CDN content, causing an infinite download retry
loop for all Linux users — including bwrap users who don't need VM
files at all.

The root cause: Patch 1 opens the yukonSilver feature gate for Linux,
making the VM download path reachable even on bwrap-only installs. The
triage bot missed this because it analyzed unpatched code.

Fix: inject empty file arrays (linux:{x64:[],arm64:[]}) instead of
copying win32 entries. This is safe because:
- The VM backend is non-functional on Linux (bwrap is the only backend)
- Empty arrays make the download loop a no-op (for...of [] skips)
- [].every() returns true (vacuous truth), reporting "Ready" status
- The linux key must exist to prevent TypeError on files["linux"]["x64"]

Removes ~230 lines of checksum infrastructure from build.sh and CI that
maintained checksums for a non-functional feature.

Fixes #334
Closes #329
Closes #332

Co-Authored-By: Claude <claude@anthropic.com>

* style: clean up stray blank line and use durable issue reference

Co-Authored-By: Claude <claude@anthropic.com>

---------

Co-authored-by: Claude <claude@anthropic.com>
2026-03-22 22:28:22 -04:00
aaddrick
bb1dd0203c style: simplify VM checksum code in PR #330
- Fix compute_checksum() stdout contamination: log messages were
  captured into variables alongside hash values; redirect to stderr
- Use EXIT trap for temp file cleanup instead of repeating rm/output
  in every early-exit path
- Remove redundant log messages in Patch 4 (replaceChecksums already
  logs its own status)

Co-Authored-By: Claude <claude@anthropic.com>
2026-03-22 08:29:49 -04:00
aaddrick
aa6b87dc52 fix: use correct linux VM checksums in cowork manifest patch (#329)
The cowork manifest patch (Patch 4) copied win32 file entries as linux
entries. Since Anthropic now publishes Linux-specific VM images with
different content, the win32 checksums cause silent validation failures
and startVM timeouts.

Compute correct SHA-256 checksums for the linux CDN files and embed
them in build.sh. Patch 4 now replaces win32 checksums with the linux
values before injecting the manifest entry. Falls back to win32 values
if linux checksums are empty.

The check-claude-version CI workflow is extended to automatically
recompute VM checksums when a new version is detected. This is
non-blocking — if CDN files aren't published yet or computation fails,
the rest of the workflow proceeds unaffected.

Fixes #329

Co-Authored-By: Claude <claude@anthropic.com>
2026-03-22 08:26:45 -04:00
aaddrick
62e7dfcdd5 style: simplify Patch 9 comments and inline variable
Co-Authored-By: Claude <claude@anthropic.com>
2026-03-20 11:17:51 -04:00
aaddrick
4711b2485e fix: don't trigger Windows VM configure on Linux (#315)
Patch 9 previously widened the win32 platform gate to include Linux,
which also activated the _.configure() call meant for Windows HCS
setup. This caused "Request timed out: configure" on Linux because
the RPC client expected an id-echoing response that the daemon didn't
provide (fixed separately in #313, but the call is unnecessary either
way).

Replace the widened gate with a separate Linux-only block injected
after the win32 block. This copies smol-bin VHDX to the bundle dir
(needed for KVM guest SDK access) without calling _.configure().

Fixes #315

Co-Authored-By: Claude <claude@anthropic.com>
2026-03-20 11:13:46 -04:00
aaddrick
b6f9f86418 Merge origin/main into feature/checksum-validation
Resolve conflict: take main's updated URLs (v1.1.7714) while
keeping the new claude_exe_sha256 variable from this branch.

Co-Authored-By: Claude <claude@anthropic.com>
2026-03-20 08:46:45 -04:00
github-actions[bot]
5f5e1b56b3 Update Claude Desktop download URLs to version 1.1.7714
Updated download URLs resolved from official redirect endpoints.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-03-20 01:37:50 +00:00
Sum Abiut
382a59ce9f refactor: simplify verify_sha256 and checksum calls
- Use bash read builtin instead of awk for hash extraction
- Use grep -F for literal filename matching (dots in node tarball)
- Remove redundant error messages (verify_sha256 already reports)
2026-03-20 11:25:10 +11:00
Sum Abiut
1fc4e837a2 feat: add SHA-256 checksum validation for downloads
Add integrity verification for downloaded artifacts in build.sh:

- New verify_sha256() helper function for reusable hash checking
- Claude Desktop EXE: verified after download (skipped for --exe)
- Node.js tarball: verified against official SHASUMS256.txt
- CI workflow updated to compute and store hex SHA-256 hashes
  alongside existing SRI hashes for Nix

Hash placeholders are empty until the next CI version update
populates them. The verify function gracefully warns and skips
when no hash is available, so builds are not blocked.

appimagetool is excluded as it uses a rolling "continuous"
release where checksums change frequently.
2026-03-20 11:14:47 +11:00
aaddrick
a5f16b2d5c feat: extract smol-bin and plugin shim from Windows installer
The Windows installer contains smol-bin.x64.vhdx (SDK binaries for
KVM guest) and cowork-plugin-shim.sh (MCP plugin sandboxing). Both
were previously unavailable on Linux.

build.sh:
- Add copy_cowork_resources() to extract both files from the
  Windows installer nupkg to Electron resources directory
- Patch index.js smol-bin copy to run on Linux (was win32-only)

cowork-vm-service.js:
- Convert smol-bin.vhdx to qcow2 at startVM time (same pattern as
  rootfs conversion)
- Check bundleDir for smol-bin before falling back to VM_BASE_DIR

Refs #288

Co-Authored-By: Claude <claude@anthropic.com>
2026-03-19 07:17:02 -04:00
github-actions[bot]
3b45c6d0e7 Update Claude Desktop download URLs to version 1.1.7464
Updated download URLs resolved from official redirect endpoints.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-03-19 01:39:30 +00:00
github-actions[bot]
d6e6c9c7ff Update Claude Desktop download URLs to version 1.1.7203
Updated download URLs resolved from official redirect endpoints.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-03-18 01:44:42 +00:00
github-actions[bot]
140d75a51d Update Claude Desktop download URLs to version 1.1.7053
Updated download URLs resolved from official redirect endpoints.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-03-17 01:42:27 +00:00
github-actions[bot]
25d87587f9 Update Claude Desktop download URLs to version 1.1.6679
Updated download URLs resolved from official redirect endpoints.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-03-14 01:38:06 +00:00
github-actions[bot]
fef878d0db Update Claude Desktop download URLs to version 1.1.6452
Updated download URLs resolved from official redirect endpoints.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-03-13 01:38:32 +00:00
github-actions[bot]
f9df6effbd Update Claude Desktop download URLs to version 1.1.6041
Updated download URLs resolved from official redirect endpoints.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-03-11 01:38:16 +00:00
github-actions[bot]
eca7c8083f Update Claude Desktop download URLs to version 1.1.5749
Updated download URLs resolved from official redirect endpoints.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-03-09 01:39:07 +00:00
github-actions[bot]
c31329e3da Update Claude Desktop download URLs to version 1.1.5368
Updated download URLs resolved from official redirect endpoints.

🤖 Generated with [Claude Code](https://claude.com/claude-code)
2026-03-06 01:38:39 +00:00
Aaddrick
b20b161b4e Merge pull request #266 from typedrat/feature/nixos-flake
feat: add NixOS flake with build.sh integration
2026-02-28 21:29:35 -05:00
Aaddrick
4929dde889 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