Two regex anchors in patch_cowork_linux() used \w+ to capture minified
identifiers, but on Claude Desktop 1.5354.0 those identifiers contain $
(e.g. C$i, g$i). \w excludes $, so the inner captures never matched:
- Patch 2b (vm: module assignment) silently no-op'd — no warning, no
failure. Build log went from "Applied 12" to "Applied 10".
- Patch 6 step 2 (retry-delay auto-launch) emitted a warning but still
failed to apply.
Either way, the resulting app.asar shipped half-patched and Cowork
startup failed at runtime with "Swift VM addon not available".
The fix widens both inner captures from \w+ to [\w$]+, matching the
existing precedent at scripts/patches/cowork.sh:482-501 (introduced in
PR #421 for the $e fs-reference rename in 1.3109.0). Also switches
Patch 6 from indexOf to lastIndexOf for the "VM service not running"
anchor — defensive against future versions reintroducing the string
outside the retry-loop site.
Verified end-to-end on Fedora 43 / KDE Plasma 6 / Wayland: build log
shows "Applied 12 cowork patches"; daemon auto-launches at startup
with clean lifecycle (startup → listen → SIGTERM exit code=0).
Follow-ups tracked in #559.
Resolves#558. Likely resolves#553 (named symptom) and #445 (daemon
never auto-spawned on Linux).
Co-authored-by: Joost-Maker <66303669+Joost-Maker@users.noreply.github.com>
Co-authored-by: HumboldtJoker <19808525+HumboldtJoker@users.noreply.github.com>
Co-authored-by: zabka <3833286+zabka@users.noreply.github.com>
Adds:
- `*)` case + valid-values warning on both `COWORK_VM_BACKEND` switches in `scripts/doctor.sh`, factored through a shared `_warn_unknown_backend` helper. Switch A explicitly matches the empty and `bwrap` cases as no-ops alongside `kvm|host` so only truly-unknown values trigger the warn. Switch B (user-facing summary) reports cowork_backend as `auto-detect (invalid override '...' — see warning above)` so the doctor is honest about what the daemon actually does (#442 tracks the daemon-side fix).
- `COWORK_VM_BACKEND` env var row + new Cowork Backend section in `docs/CONFIGURATION.md`, placed before Cowork Sandbox Mounts.
- VM connection timeout / virtiofsd PATH / Fedora tmpfs (EXDEV) sections in `docs/TROUBLESHOOTING.md`.
- README acknowledgment for @CyPack.
Closes#293
Co-Authored-By: aaddrick <aaddrick@gmail.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(autostart): route openAtLogin through XDG Autostart on Linux (#128)
Electron's app.getLoginItemSettings()/setLoginItemSettings() are
no-ops on Linux (electron/electron#15198), so the "Run on startup"
toggle never persists and isStartupOnLoginEnabled() returns
undefined, failing the IPC handler's typeof === 'boolean' check.
Intercept both calls in frame-fix-wrapper.js and back them with
~/.config/autostart/claude-desktop.desktop, which is honoured by
GNOME/KDE/XFCE/Cinnamon/MATE/LXQt (XDG Autostart spec). Also
coerce executableWillLaunchAtLogin (Windows-only in Electron,
undefined on Linux) to a boolean so the IPC handler stops
throwing.
Fixes#128
Co-Authored-By: Claude <claude@anthropic.com>
* fix(autostart): address review — APPIMAGE runtime target, XDG_CONFIG_HOME, StartupWMClass (#128)
Addresses review comments on #450:
- Resolve Exec= and Icon= at toggle time via process.env.APPIMAGE
so AppImage users (who don't have claude-desktop on $PATH unless
integrated via AppImageLauncher) get an autostart entry that
launches the actual .AppImage bundle instead of a broken binary
reference. escapeExecArg() handles Desktop Entry Exec escaping
(quote + backslash-escape reserved chars).
- Honour $XDG_CONFIG_HOME when set and non-empty, falling back to
~/.config only otherwise. Home-manager and dotfile users who
relocate the config root were getting the entry dropped in the
wrong place silently.
- Add StartupWMClass=Claude to the generated entry, matching the
value set by scripts/packaging/{deb,rpm}.sh, so DEs group the
autostarted window with user-launched instances under a single
taskbar/dock item. Drop Categories= per review guidance
(autostart parsers ignore it).
- Comment why opts.path is intentionally ignored: process.execPath
points at the electron binary, not the launcher shim that sets
ELECTRON_FORCE_IS_PACKAGED / ozone flags / orphan cleanup —
honouring opts.path would write a broken autostart entry.
The "removed" log placement (review item 4) is already inside the
inner try, so unlinkSync throwing ENOENT short-circuits before the
log runs. Left as-is.
Co-Authored-By: Claude <claude@anthropic.com>
* docs(readme): credit lizthegrey for XDG Autostart contribution
Co-Authored-By: Claude <claude@anthropic.com>
---------
Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: aaddrick <aaddrick@gmail.com>
* fix(lifecycle): hide main window to tray on close, Linux (#448)
Electron's default window-all-closed handler quits the app on
Linux. The existing tray icon and Ctrl+Q patches keep the app
reachable while a window is alive, but as soon as the last
window is closed (stray click on X, or a sign-out flow that
closes mainWindow) the app exits and the tray goes with it —
taking any in-app schedulers / MCP servers / cron tasks
(/schedule skill) down silently until the user re-launches.
Intercept BrowserWindow.close on main windows (not popups;
Quick Entry and About already dismiss via hide(), never emit
close) and preventDefault + hide unless app is in a real quit
path. The quit path is detected via before-quit: Ctrl+Q, tray
Quit, cmd+Q, SIGTERM and app.quit() from anywhere all emit
before-quit, which arms app._quittingIntentionally so the
close handler lets the window actually close.
Gated by CLOSE_TO_TRAY, default on. Set CLAUDE_QUIT_ON_CLOSE=1
to restore the Electron-default behaviour.
Fixes#448
Co-Authored-By: Claude <claude@anthropic.com>
* fix(frame-fix-wrapper): drop superseded globalShortcut Ctrl+Q
Removes the globalShortcut.register('CommandOrControl+Q') block
that #484 superseded with the per-window
webContents.on('before-input-event') listener. Auto-merging main
into this branch left both registrations in place, which would
re-introduce the AZERTY physical-keycode grab and system-wide
shortcut steal that #484 fixed. The focus-scoped listener
already covers the original #321 hidden-menu-bar use case.
Also updates the close-to-tray comment to reference the new
listener path instead of the removed global shortcut.
Co-Authored-By: Claude <claude@anthropic.com>
* docs(readme): credit lizthegrey for close-to-tray contribution
Co-Authored-By: Claude <claude@anthropic.com>
---------
Co-authored-by: Claude <claude@anthropic.com>
Co-authored-by: aaddrick <aaddrick@gmail.com>
* fix: update visibility function regexp
* fix(quick-window): tolerate optional var decl in visibility regex
Make the `var <name>(,<name>)*;` prefix optional so the regex
matches both the older shape (`function L7A(){return!Ct...}`,
1.3109.0) and the current one (`function aZA(){var e;return!Qt...}`,
1.3883.0). The minifier hoists `var e;` whenever the function body
uses optional chaining; if a future release adds `var e,t;` or
drops the var entirely, this still matches without another
chase-the-shape PR.
Verified end-to-end on the live 1.3883.0 build asar: extracts
`pF` / `aZA`, patches both Quick Entry anchor sites
("Navigating to existing chat", "Creating new chat with
submit_quick_entry"), JS validates, idempotent re-run confirmed.
Confirmed against the 1.3109.0 build-reference shape too.
Repro of #390 on Nobara KDE Plasma 6 (Wayland): quick-entry
submit now reliably shows the main window post-patch; no
regressions in regular chat or window restore flows.
Co-Authored-By: Claude <claude@anthropic.com>
* docs(readme): credit @Andrej730 for visibility regex fix (#495)
Co-Authored-By: Claude <claude@anthropic.com>
---------
Co-authored-by: aaddrick <aaddrick@gmail.com>
Co-authored-by: Claude <claude@anthropic.com>
* 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>
Top-of-README callout swapped from the EXPERIMENTAL Cowork Mode
summary to a short pointer advisory for existing APT users whose
sources.list still targets aaddrick.github.io.
Rationale: the cowork block is accurate but describes routine
operational behavior; the migration advisory describes an imminent,
user-visible break on next `apt update` that needs action. The
detailed migration instructions live in the Installation section
(#510), so this callout is just a pointer, not duplication.
DNF users don't need to do anything (DNF follows the downgrade
silently); called out explicitly to avoid unnecessary sed-by-reflex.
Refs #493
Phase 4a-APT cutover (#493, #503) moves binary distribution behind a
Cloudflare Worker at pkg.claude-desktop-debian.dev. The Worker serves
repo metadata directly and 302-redirects .deb/.rpm requests to GitHub
Release assets, which makes the >100 MB .deb push cap irrelevant.
GitHub Pages auto-301s legacy aaddrick.github.io/claude-desktop-debian
URLs to pkg.claude-desktop-debian.dev, but the redirect uses http://
(Pages has no cert for pkg.<domain> — DNS points at Cloudflare, so
Pages can never pass domain verification). apt refuses that scheme
downgrade as a security policy, so existing users' sources.list
silently breaks on the next `apt update`. DNF accepts the downgrade
and keeps working.
Changes:
- README.md: install snippets (APT + DNF) now point at
pkg.claude-desktop-debian.dev directly. New users never touch the
Pages redirect chain.
- README.md: add a "Migrating from the old aaddrick.github.io URL"
section with sed one-liners for existing users + a short background
paragraph explaining why the change was needed.
- .github/workflows/ci.yml: release-notes install snippets (APT + DNF,
both branches) and the generated claude-desktop.repo file's baseurl
and gpgkey all point at pkg.<domain>. Smoke-test chain walkers
deliberately keep starting at github.io (they test the full 3-hop
Pages→Worker→Releases chain for clients that do follow the
downgrade, like curl-without-L and dnf).
Refs #493, #503
* fix(cowork): forward CLAUDE_CODE_OAUTH_TOKEN to VM spawn env (#482)
buildSpawnEnv and BwrapBackend.spawn both stripped every CLAUDE_CODE_*
env var from the daemon's process.env via filterEnv(process.env,
['CLAUDE_CODE_']) -- including CLAUDE_CODE_OAUTH_TOKEN, the standard
auth channel for the in-VM claude binary. The bwrap sandbox mounts
home as an empty --dir, so ~/.claude/.credentials.json is inaccessible
inside; env is the only viable auth path. Result: every shell tool
call returned "Not logged in. Please run /login".
Upstream's seA() does include the token in the spawn env it assembles,
but on Linux that payload isn't reaching the daemon's params.env, so
the daemon's inherited process.env is the only surviving source.
Stripping it severed auth.
Introduce FORWARDED_ENV_KEYS = ['CLAUDE_CODE_OAUTH_TOKEN'] plus a
forwardAuthEnv helper that re-adds the token from process.env when
appEnv doesn't provide one. Extract buildBaseSpawnEnv as the shared
env-construction path for both spawn sites so the filter/forward logic
can't drift.
Diagnosis and reference diff by @pb3ck in #482. This PR extends the
fix to BwrapBackend.spawn (the second call site), factors the shared
helper, and adds regression coverage.
Co-Authored-By: Claude <claude@anthropic.com>
* refactor(cowork): fold forwardAuthEnv into buildBaseSpawnEnv
The forwardAuthEnv helper had a single call site inside
buildBaseSpawnEnv. Inlining the forward loop removes a layer of
indirection for a private helper and consolidates the empty-string
guard comment next to the code it documents.
No behavior change. All 72 tests in cowork-path-translation.bats
still pass, including the four #482 regression tests.
Co-Authored-By: Claude <claude@anthropic.com>
* docs(readme): credit @pb3ck for #482 diagnosis
Co-Authored-By: Claude <claude@anthropic.com>
---------
Co-authored-by: Claude <claude@anthropic.com>
XRDP sessions lack GPU acceleration; Electron's default GPU compositing
renders a blank window. Detect XRDP via the $XRDP_SESSION env var and
systemd-logind's session Type, then append --disable-gpu and
--disable-software-rasterizer to the Electron args.
Based on @davidamacey's fix in #320, with the detection hardened: we
use `loginctl show-session -p Type --value` instead of probing for
xrdp-sesman, because that daemon runs on any host with xrdp installed
and would false-positive on local sessions.
Adds tests/launcher-xrdp-detection.bats with 8 cases covering both
positive and negative detection paths, including the XDG_SESSION_ID-
unset and loginctl-nonzero edge cases.
Fixes: #319
Co-authored-by: davidamacey <davidamacey@gmail.com>
Co-authored-by: Claude <claude@anthropic.com>
Directory scaffolding + skeleton workflow + issue templates. No live
behavior — v2 remains workflow_dispatch-only with `permissions: {}` and
a single job that echoes the issue number. v1 (`issue-triage.yml`) is
untouched.
Per docs/issue-triage/implementation-plan.md Phase 0:
- `.github/workflows/issue-triage-v2.yml` — skeleton workflow
- `.github/ISSUE_TEMPLATE/{config,bug_report,feature_request}.yml` —
shapes input for the Stage 2 classifier and Stage 4 investigator;
privacy disclosure in a non-editable markdown info block
- `.claude/scripts/prompts/.gitkeep` — prompts land per-phase
- `.claude/scripts/taxonomies/label-blocklist.json` — Stage 9 suggested-
label gating (wontfix, invalid, duplicate, help wanted, good first
issue); additional taxonomies land in Phase 4
- `.claude/scripts/reasons.json` — Stage 8b deferral-reason SSOT
consumed by the renderer and post-processor (six entries)
- README Privacy section — keeps disclosure text discoverable without
filing an issue; matches the templates' info block
Exit criteria: dispatch against any issue number prints correctly; no
API calls, no comments, no labels; `bug_report.yml` / `feature_request
.yml` render cleanly with the privacy block.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix: diagnose AppArmor userns block on bwrap probe (#351)
Ubuntu 24.04+ ships apparmor_restrict_unprivileged_userns=1 by
default, which blocks the user namespace bwrap needs to start. The
daemon's probe then fails, auto-detect silently falls through to
KVM, and KVM hangs waiting for a rootfs the user hasn't set up —
leaving Cowork stuck in a retry loop with no clear error.
- Classify the probe failure (classifyBwrapProbeError) so the daemon
can distinguish AppArmor/userns blocks from generic failures and
log a pointer to the TROUBLESHOOTING.md remediation.
- Stop falling through to KVM when bwrap is installed but blocked;
drop to host-direct instead so users see a working (if unsandboxed)
Cowork and the reason bwrap didn't engage. Users who actually want
KVM can still set COWORK_VM_BACKEND=kvm.
- Mirror the probe + diagnosis in `--doctor` so misconfigured systems
get the same actionable output without waiting for a daemon log.
- Document the AppArmor profile workaround in TROUBLESHOOTING.md.
- Credit @hfyeh for the diagnosis and profile snippet.
Co-Authored-By: Claude <claude@anthropic.com>
* refactor: simplify PR #434 per cdd-code-simplifier
Drop redundant `-n` guard around the COWORK_VM_BACKEND case in
`--doctor`: the `${VAR,,}` expansion is already safe on an unset var
(no `set -u` in this script) and the `kvm|host` arms simply don't
match an empty string.
Co-Authored-By: Claude <claude@anthropic.com>
---------
Co-authored-by: Claude <claude@anthropic.com>
* fix: launcher-common.sh self-match and stale socket cleanup (#407)
Three related bugs in scripts/launcher-common.sh that combine to break
Claude Desktop startup after any crash that reparents the cowork daemon
on Debian/Ubuntu/Mint systems.
1. cleanup_orphaned_cowork_daemon — the old pgrep pattern
'claude-desktop' self-matches the launcher's own bash process
(cmdline `bash /usr/bin/claude-desktop`), causing the function to
return early on every invocation. The SIGTERM loop never runs.
Replaced with `pgrep -f 'app\.asar'` plus $$/$PPID exclusion,
--type= filter (skips chromium helpers), and /proc/*/status check
(skips stopped/zombie launcher bashes). Added SIGKILL escalation
after ~2s so cleanup_stale_cowork_socket reliably sees no daemon.
2. cleanup_stale_cowork_socket — the old implementation required
socat (not preinstalled on Debian/Ubuntu/Mint) and fell through
to a find -mmin +1440 check that ignored any socket younger
than 24h. Rewritten to use the ordering invariant:
cleanup_orphaned_cowork_daemon runs first and kills any orphan,
so at this point an extant daemon proves the socket is live and
an absent daemon proves the socket is stale. No socat dependency.
3. run_doctor orphan check — same self-match flaw as (1).
claude-desktop --doctor reported [PASS] Cowork daemon: running
(parent alive) on systems with a genuine orphan, actively
misleading users trying to diagnose this failure. Applied the
same detection primitive as (1).
Complements #410 (daemon-side crash recovery): #410 reduces how
often orphans are created; this ensures the launcher actually cleans
them up when they are.
Fixes#407
Co-Authored-By: martin152 <martin152@users.noreply.github.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* docs: credit martin152 in Acknowledgments for #407 launcher fix
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* style: quote RHS of $$/$PPID comparisons (SC2053)
Shellcheck SC2053: quote RHS in [[ ]] equality tests to prevent glob
matching. No behavior change — $$ and $PPID are always numeric PIDs.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: martin152 <martin152@users.noreply.github.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Update Acknowledgments section for readability — contributors with
multiple items now use nested bullet lists instead of inline commas.
Also adds cbonnissent's configurable bwrap mount points contribution.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
VM file downloads were disabled on Linux in #337, making the KVM
backend non-functional. Remove KVM from the backend table and add
a status note explaining why.
Co-Authored-By: Claude <claude@anthropic.com>
Swap auto-detection order from KVM → bwrap → host to
bwrap → KVM → host. KVM remains available via
COWORK_VM_BACKEND=kvm.
- detectBackend(): check bwrap before KVM
- --doctor: bwrap checked first; KVM deps shown as info
(not warnings) unless COWORK_VM_BACKEND=kvm is set
- Fix header comment inaccuracy about rootfs.qcow2 check
- Update README and handover docs to reflect new default
Fixes#326
Co-Authored-By: Claude <claude@anthropic.com>
All three launchers (deb, RPM, AppImage) used bare `exit` after
`run_doctor`, discarding the return code. Now uses `exit $?` so
non-zero exit codes from doctor diagnostics propagate correctly.
Also adds joekale-pp to README contributors for RPM doctor support.
Co-Authored-By: Claude <claude@anthropic.com>
Follow-up to PR #299:
- Log original env var value instead of lowercased in alias resolution
- Use single quotes for literal string per style guide
- Document yes/no aliases in CONFIGURATION.md table
- Update noctuum's contributor entry for boolean alias work
Co-Authored-By: Claude <claude@anthropic.com>
- Pin DeterminateSystems/nix-installer-action to v21 instead of @main
- Use `nix flake update --flake .` to avoid deprecation warning
- Align github-actions[bot] email format with check-claude-version.yml
- Add typedrat to Acknowledgments for NixOS flake contribution (#266)
Co-Authored-By: Claude <claude@anthropic.com>
* 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>