* launcher: log session/IME env block at startup (#548)
Adds log_session_env, called once per launch from each packaging
target (deb, rpm, AppImage, Nix). Emits a single env={ ... } block
covering display (XDG_SESSION_TYPE, WAYLAND_DISPLAY, DISPLAY,
XDG_CURRENT_DESKTOP), IME (GTK_IM_MODULE, XMODIFIERS, QT_IM_MODULE),
and Claude-specific overrides (CLAUDE_USE_WAYLAND,
CLAUDE_TITLEBAR_STYLE, CLAUDE_GTK_IM_MODULE).
Empty/unset values are emitted as `KEY=` (rather than omitted) so
absence is unambiguous in bug reports. Pure observability — no
behavior change.
Closes#548
Co-Authored-By: Claude <claude@anthropic.com>
* test: consolidate log_session_env BATS coverage (#548)
Collapse the four log_session_env cases into two, and tighten the
assertions in both:
- Old test 1 (substring match per key) + old test 4 (block braces
on their own lines) → one test using exact-line equality on the
`lines` array. Locks block structure and per-key formatting in
a single pass; substring matching could not catch a regression
that re-ordered keys, dropped indentation, or merged lines.
- Old test 2 (unset values are KEY=) + old test 3 (empty-string
is KEY=) → one test covering both code paths. Exact-line match
proves the value after `=` is truly empty; the previous
`*'KEY='*` substring would have matched `KEY=value` and the
old test-3 regex was fragile (depended on trailing newline
being literal `$'\n'` vs end-of-string `$`).
Net: 77 → 42 lines, 4 → 2 cases, stronger guarantees. No change to
the helper itself or the call sites — issue #548 acceptance criteria
still hold.
---------
Co-authored-by: Claude <claude@anthropic.com>
The Claude Desktop app registers a custom app:// protocol handler rooted
at process.resourcesPath/ion-dist — that directory holds the static SPA
assets for internal windows like Third-Party Inference setup, Connectors
config, etc. The Windows installer ships ion-dist under lib/net45/resources
but scripts/staging/cowork-resources.sh never copied it into the electron
resources dest, so every app://localhost/* request fell through to the
static file handler's index.html fallback, which also failed because the
whole directory was missing.
Net effect: the Third-Party Inference setup window (Developer → Configure
Third-Party Inference…) opened as a blank window with
ERR_FILE_NOT_FOUND / ERR_UNEXPECTED on loadURL.
Add an ion-dist copy step to copy_cowork_resources() matching the
existing smol-bin / plugin-shim pattern (warn-and-continue on absence),
and a matching install stanza in nix/claude-desktop.nix so NixOS users
get the fix too — without the Nix hunk, ion-dist lands in the
nix-resources/ sentinel but the installPhase doesn't cherry-pick it.
Verified end-to-end:
- Fresh build ./build.sh --build appimage picks up ion-dist from the
1.3883.0 Windows installer (84 MB uncompressed, ~42 MB delta in the
AppImage after squashfs).
- Live install on CachyOS daily-driver; Developer → Configure Third-Party
Inference… now renders the full 6-tab config UI (Connection / Sandbox
& workspace / Connectors & extensions / Telemetry & updates / Usage
limits / Plugins & skills / Egress Requirements) per the upstream
docs at support.claude.com/en/articles/14680741.
- Logs clean of ERR_UNEXPECTED / ERR_FILE_NOT_FOUND on setup-desktop-3p.
Fixes#488
Co-authored-by: Claude <claude@anthropic.com>
Moves run_doctor and its 9 internal helpers out of launcher-common.sh
(~670 lines) into their own scripts/doctor.sh. launcher-common.sh
now sources doctor.sh via a BASH_SOURCE-relative path, so any consumer
still gets the run_doctor entry point without needing to know about
the split.
Rationale: the testing / release-quality role concerns itself with
--doctor, and giving that subsystem its own file lets CODEOWNERS
scope it independently of the rest of launcher-common (display
detection, cleanup handlers, electron env) which remain in aaddrick's
domain.
Each packaging target now installs doctor.sh alongside launcher-common.sh:
scripts/packaging/appimage.sh → /usr/lib/claude-desktop/{launcher-common,doctor}.sh
scripts/packaging/deb.sh → /usr/lib/<pkg>/{launcher-common,doctor}.sh
scripts/packaging/rpm.sh → /usr/lib/<pkg>/{launcher-common,doctor}.sh
nix/claude-desktop.nix → $out/lib/claude-desktop/{launcher-common,doctor}.sh
Pure-move refactor. Function bodies byte-identical to the pre-split
launcher-common.sh content. Verified: `source launcher-common.sh` still
defines all 19 previous functions (9 launcher + 10 doctor); a live
run_doctor invocation produces the same output as before.
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>
After a crash or unclean exit, the cowork-vm-service daemon can
outlive the main Electron UI process. The orphaned daemon holds
LevelDB locks in ~/.config/Claude/Local Storage/ which cause new
launches to detect a main instance and silently exit with
Not main instance, returning early from app ready.
Add cleanup_orphaned_cowork_daemon() that detects daemon processes
without a living parent UI process and terminates them before the
existing stale lock/socket cleanup runs.
Also add a --doctor diagnostic check that warns when an orphaned
cowork daemon is detected.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
On NixOS, Electron is a separate Nix store path from the app, so
process.resourcesPath points to Electron's resources dir rather than
the app's. Claude's app code uses app.isPackaged to choose between
resourcesPath-based and development-style fallback paths. Setting
ELECTRON_FORCE_IS_PACKAGED=true (introduced by PR #305) forces the
resourcesPath code paths, which resolve to the wrong location on
NixOS, causing a silent startup hang.
Skip the env var for 'nix' package type so isPackaged remains false,
restoring the working behavior from before PR #305.
Fixes#311
Co-Authored-By: Claude <claude@anthropic.com>
The Nix build used makeWrapper which bypassed launcher-common.sh
entirely. NixOS users had no --doctor support, no CLAUDE_USE_WAYLAND
env var handling, no display detection, and no startup logging.
Replace makeWrapper with a full bash launcher script that sources
launcher-common.sh, matching the deb/RPM/AppImage launchers:
- --doctor diagnostic support
- CLAUDE_USE_WAYLAND env var for native Wayland mode
- Display backend auto-detection
- Stale lock and socket cleanup
- Startup logging to ~/.cache/claude-desktop-debian/launcher.log
- Cowork resources (smol-bin, plugin shim) installed to resources
Also adds 'nix' package type to build_electron_args for proper
--no-sandbox handling on Wayland.
Refs #282
Co-Authored-By: Claude <claude@anthropic.com>
- Filter sourceRoot with lib.cleanSourceWith to exclude build-reference/,
logs/, test-build/, squashfs-root/, and result* from the Nix store
- Add -n guard for node_pty_build_dir in finalize_app_asar elif branch
- Update Nix package to version 1.1.4498 with current URLs and SRI hashes
- Download SRI hash inputs to temp files to catch partial download failures
- Extract nested command substitutions into variables for CI sed patterns
- Add structural assumption comment for range-based sed hash updates
- Validate --source-dir and --node-pty-dir paths early with clear errors
- Support SSH helpers in nix builds via nix-resources staging directory
Co-Authored-By: Claude <claude@anthropic.com>
- Wire up --node-pty-dir flag in install_node_pty and finalize_app_asar
(was parsed but never used; nix builds had no node-pty binaries)
- Set electron_resources_dest for nix builds so process_icons has a
tray icon destination; update nix installPhase to match
- Remove unused lib param from fhs.nix
- Add missing source_dir global declaration
- Fix flake.nix whitespace
Co-Authored-By: Claude <claude@anthropic.com>
copy_locale_files is skipped in nix mode (it depends on
stage_electron setting electron_resources_dest). Copy the locale
JSONs directly from the extracted installer into $out resources
as a safety net — they're also inside app.asar at resources/i18n/.
Co-Authored-By: Claude <claude@anthropic.com>
Build node-pty from source using buildNpmPackage so Claude Desktop's
terminal features (Claude Code) work on NixOS. The derivation handles
three issues with the upstream package:
- Strips macOS-only fsevents reference from package-lock.json so
npm ci doesn't fail on lock/json mismatch
- Runs both tsc (TypeScript to JS) and node-gyp rebuild (native addon)
- Copies build/Release/pty.node into output since npmInstallHook
doesn't include native addons
Also wires node-pty into claude-desktop.nix via --node-pty-dir flag
and updates the flake overlay to expose it.
Co-Authored-By: Claude <claude@anthropic.com>