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>
* 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>
* 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>
* 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>
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>
buildSpawnEnv() translates CLAUDE_CONFIG_DIR from /sessions/ guest paths
to host paths, but CLAUDE_COWORK_MEMORY_PATH_OVERRIDE passes through
untranslated. On HostBackend, the /sessions/ directory does not exist,
so the auto-memory path points to a non-existent location and memory
writes silently fail.
This adds the same guest-path translation for the memory override:
1. Try translateGuestPath() (works if .auto-memory is in mountMap)
2. Fall back to resolveSubpath() on the mount-name portion, mirroring
what HostBackend.mountPath() would return (typically ~/.auto-memory)
3. Remove the env var if neither translation succeeds
BwrapBackend is unaffected — it overrides spawn() with its own env
construction and /sessions/ paths are real inside the sandbox.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: read Electron version from file instead of launching binary (#371)
--doctor hung because launching the Electron binary to get its version
spawns the full app, which ignores SIGPIPE and never exits. Read the
version file next to the binary instead — instant and reliable.
Co-Authored-By: Claude <claude@anthropic.com>
* style: extract _electron_version helper to deduplicate version reading
Both the bundled and system Electron code paths performed the same
version-file lookup inline. Extract to a shared helper for clarity.
Co-Authored-By: Claude <claude@anthropic.com>
---------
Co-authored-by: Claude <claude@anthropic.com>
The previous commit double-encoded UTF-8 em-dash characters (U+2014)
due to btoa/atob Latin-1 handling. This commit re-applies all patches
from a clean upstream base with proper UTF-8 encoding.
Co-authored-by: Claude <noreply@anthropic.com>
PR #374 fixed the 3 mountPath() methods but missed 4 other call sites
that also join os.homedir() with root-relative subpaths from app.asar.
This commit:
- Adds resolveSubpath() helper that handles both root-relative and
home-relative subpaths correctly
- Fixes buildMountMap() doubled mount paths
- Fixes buildSpawnEnv() doubled CLAUDE_CONFIG_DIR (critical: this is
where Claude Code stores conversation data; deleting ~/home/ after
the incomplete fix crashed session resume)
- Fixes resolveWorkDir() doubled working directory
- Fixes resolveSdkBinary() doubled SDK binary path
- Upgrades the 3 mountPath() methods to use resolveSubpath() for
consistency and correct handling of home-relative paths
Fixes#373
The mountPath() methods in HostBackend, BwrapBackend, and KvmBackend
joined os.homedir() with a root-relative subpath, causing paths like
/home/user/home/user/.config/Claude/... instead of the correct
/home/user/.config/Claude/...
The subpath parameter is encoded as path.relative("/", absolutePath),
making it root-relative. Joining with "/" instead of os.homedir()
produces the correct absolute path.
Fixes#373
Co-Authored-By: Claude <claude@anthropic.com>
https://claude.ai/code/session_01TEWYXVLaKgBfKVkHY47g9M
Bypasses the AI-powered compare-releases step to reduce API costs.
Falls back to the existing generic release notes template.
Co-Authored-By: Claude <claude@anthropic.com>
Two changes for quit accessibility on Linux:
1. Fix Alt menu bar toggle in 'auto' mode (the default). The show
event handler and setApplicationMenu interceptor were force-hiding
the menu bar on every event, overriding autoHideMenuBar's native
Alt toggle. Now only 'hidden' mode force-hides; 'auto' lets
Electron handle the toggle natively.
2. Register Ctrl+Q as a global shortcut to quit. The upstream menu
has a CmdOrCtrl+Q accelerator but Electron doesn't fire menu
accelerators when the menu bar is hidden on Linux. The global
shortcut ensures Ctrl+Q always works, using the same API as
Ctrl+Alt+Space (works under XWayland).
Together these give GNOME and other DE users two ways to quit
without needing a tray icon: Alt → File → Quit, or Ctrl+Q.
Fixes#321
Co-Authored-By: Claude <claude@anthropic.com>
Add early exit when all tools are already installed, and use sudo -n
(non-interactive) throughout both hook scripts to fail immediately
instead of hanging on password prompts. Applies to session-start.sh
and install-build-tools.sh.
Fixes#359
Co-Authored-By: Claude <claude@anthropic.com>
Create docs/learnings/ for hard-won technical knowledge that isn't
obvious from code or docs alone. Reference from CLAUDE.md so
contributors (human and AI) consult it before working on related areas.
First entry covers NixOS Electron resource path resolution,
/proc/self/exe symlink behavior, testing without NixOS, and why
the co-located binary approach was chosen over alternatives.
Co-Authored-By: Claude <claude@anthropic.com>
Use [[ ]] for conditionals, collapse single-line if/fi blocks,
remove redundant mkdir, fix double-space in exec line.
Co-Authored-By: Claude <claude@anthropic.com>
On NixOS, Electron and the app live in separate Nix store paths.
When ELECTRON_FORCE_IS_PACKAGED=true, the app reads locale files
(en-US.json) from process.resourcesPath at module load time —
before frame-fix-wrapper.js can correct the path. Since
resourcesPath is computed from /proc/self/exe (which resolves to
electron-unwrapped's store path), the files aren't found and the
app crashes with ENOENT.
The fix copies the Electron ELF binary into a custom tree within
the derivation, then merges both Electron's and the app's resources
into the adjacent resources/ directory. Everything else (shared
libs, .pak files, locales/) is symlinked to avoid duplication.
This makes /proc/self/exe resolve to our tree, so resourcesPath
naturally contains all needed files.
Also enables ELECTRON_FORCE_IS_PACKAGED=true unconditionally for
all package types, removing the 'nix' special case that kept NixOS
running in development mode with debug logging and exposed IPC.
Fixes#316
Co-Authored-By: Claude <claude@anthropic.com>
The OOM fix is working — the script survives the full pipeline now. But
498 hunks of Claude-powered analysis need more than 5 minutes. Increase
timeout to 180 minutes so AI-generated release notes can complete. The
fallback and if: always() hardening remain as safety net.
Co-Authored-By: Claude <claude@anthropic.com>
OOM fix is in progress in claude-desktop-versions. Re-enabling so the
next release tests the fix. The if: always() hardening on fallback and
release steps ensures the release still ships if the script fails.
Co-Authored-By: Claude <claude@anthropic.com>
The concurrency group fix was insufficient — the runner SIGTERM occurs
even with a single CI run. The compare-releases.py script itself causes
the runner to die (~86s, exit 143) regardless of concurrency. Disabling
the step entirely until the script is debugged in claude-desktop-versions.
The fallback notes and if: always() hardening remain in place.
Co-Authored-By: Claude <claude@anthropic.com>
Add concurrency group to CI workflow so concurrent runs (triggered when
check-claude-version pushes to main then pushes a tag) queue instead of
killing each other. This addresses the ~86-second runner SIGTERM that
has blocked 10 releases in March.
Also harden release steps as defense-in-depth:
- timeout-minutes: 5 on compare-releases step
- if: always() on fallback notes and Create GitHub Release steps
Co-Authored-By: Claude <claude@anthropic.com>
The packages attrset referenced claude-desktop-fhs for the default
attribute, but without rec the name wasn't in scope. Move the
definition to the let block and use inherit instead.
Co-Authored-By: Claude <claude@anthropic.com>