Remove the npm-install-build-system wrapper and the Node.js dependency
in conf/home/pi.scm.
-* Move configuration files to the Guix store and bundle them with the
- binary. This requires redesigning home-program-configuration so
- dotfiles are part of the package derivation rather than symlinked via
- home-symlink-service-type.
+* When Claude Code supports separating config from runtime state (e.g.
+ separate CLAUDE_STATE_DIR env var), remove the symlink and --settings
+ flag from the claude-code wrapper; point CLAUDE_CONFIG_DIR directly
+ to $out/etc.
* When Paseo triggers Claude Code from mobile, get-api-key runs pass(1) which
prompts for a GPG passphrase on a headless terminal — the session hangs.
key lives on the phone behind Paseo's relay; the key needs to cross that
relay into the laptop somehow, which there is currently no mechanism for.
-* Claude Code defaults to ~/.claude for its config directory but we store
- it at ~/.config/claude (XDG), so CLAUDE_CONFIG_DIR must be set to
- redirect it. Remove the explicit CLAUDE_CONFIG_DIR from the paseo
- Shepherd service and find a generic way to set it for all daemons that
- spawn Claude Code.
* Use SetCwd instead of spawning a subshell with cd(1) in hooks once
https://github.com/anthropics/claude-code/issues/69159 is resolved.
;; Copyright (c) 2026 Jakub Czajka <jakub@ekhem.eu.org>
;; License: GPL-3.0 or later.
;;
-;; claude.scm - Claude Code package and home service with dotfiles.
-;;
-;; The upstream binary is prebuilt for systems with Filesystem Hierarchy
-;; Standard and expects a standard linker path. GNU Guix uses non-standard store
-;; paths, so we wrap the binary in a shell script that sets LD_LIBRARY_PATH to
-;; the store locations of its dependencies and invokes it through ld-linux.
+;; claude.scm - Claude Code package with bundled configuration.
(define-module (conf home claude)
#:use-module (guix packages)
#:use-module (gnu packages tls)
#:use-module (gnu packages compression)
#:use-module (gnu packages bash)
- #:use-module (conf home program)
- #:use-module (gnu services)
- #:export (claude-code claude-service))
+ #:use-module (gnu packages elf)
+ #:export (claude-code))
+
+(define settings-file
+ (local-file (canonicalize-path (string-append (getenv "GUIX_PACKAGE_PATH")
+ "/claude/.config/claude/settings.json"))
+ "settings.json"))
+
+(define instructions-file
+ (local-file (canonicalize-path (string-append (getenv "GUIX_PACKAGE_PATH")
+ "/claude/.config/claude/CLAUDE.md"))
+ "CLAUDE.md"))
+
+(define api-key-file
+ (local-file (canonicalize-path (string-append (getenv "GUIX_PACKAGE_PATH")
+ "/claude/.local/bin/get-api-key"))
+ "get-api-key"))
(define claude-code
(package
(use-modules (guix build utils))
(let* ((out #$output)
(bin (string-append out "/bin"))
- (internal-bin (string-append out "/libexec"))
- (real-binary (string-append internal-bin "/claude"))
- (wrapper (string-append bin "/claude"))
- (bash (string-append #$bash-minimal "/bin/bash"))
- (ld-linux (string-append #$glibc "/lib/ld-linux-x86-64.so.2"))
- ;; Define runtime library path for the wrapper.
- (lib-path (string-append #$glibc
- "/lib:"
- #$gcc:lib
- "/lib:"
- #$gcc:lib
- "/lib64:"
- #$openssl
- "/lib:"
- #$zlib
- "/lib")))
- ;; 1. Copy the raw binary into an isolated internal path.
- (mkdir-p internal-bin)
- (copy-file #$source real-binary)
- (chmod real-binary #o755)
- ;; 2. Generate a shell wrapper that invokes the binary
- ;; using Guix's linker.
+ (etc (string-append out "/etc"))
+ (binary (string-append bin "/claude"))
+ (api-helper (string-append etc "/get-api-key"))
+ (patchelf #$(file-append patchelf "/bin/patchelf"))
+ (ld-so #$(file-append glibc "/lib/ld-linux-x86-64.so.2"))
+ (rpath (string-append #$glibc
+ "/lib:"
+ #$gcc:lib
+ "/lib:"
+ #$gcc:lib
+ "/lib64:"
+ #$openssl
+ "/lib:"
+ #$zlib
+ "/lib")))
+ ;; 1. Copy the binary and patch its ELF.
(mkdir-p bin)
- (with-output-to-file wrapper
- (lambda ()
- (format #t "#!~a~%" bash)
- (format #t "export LD_LIBRARY_PATH=\"~a:" lib-path)
- (format #t "$LD_LIBRARY_PATH\"~%")
- ;; Invoke the explicit linker, passing CLI args.
- (format #t "exec ~a ~a \"$@\"~%" ld-linux real-binary)))
- (chmod wrapper #o755)))))
+ (copy-file #$source binary)
+ (chmod binary #o755)
+ (invoke patchelf "--set-interpreter" ld-so binary)
+ (invoke patchelf "--set-rpath" rpath binary)
+ ;; 2. Copy and patch configuration files.
+ (mkdir-p etc)
+ (copy-file #$instructions-file
+ (string-append etc "/CLAUDE.md"))
+ (copy-file #$settings-file
+ (string-append etc "/settings.json"))
+ (substitute* (string-append etc "/settings.json")
+ (("\\$\\{HOME\\}/\\.local/bin/get-api-key")
+ api-helper))
+ ;; 3. Copy the API key helper.
+ (copy-file #$api-key-file api-helper)
+ (chmod api-helper #o755)
+ ;; 4. Wrap the binary to set CLAUDE_CONFIG_DIR.
+ ;; wrap-program needs bash in PATH for the shebang.
+ (setenv "PATH"
+ (string-append #$bash-minimal "/bin"))
+ (wrap-program binary
+ `("CLAUDE_CONFIG_DIR" =
+ (,etc)))))))
(inputs (list glibc
- `(,gcc "lib") openssl zlib bash-minimal))
+ `(,gcc "lib")
+ openssl
+ zlib
+ bash-minimal
+ patchelf))
(home-page "https://claude.ai")
(synopsis "Agentic coding tool that lives in your terminal")
(description "Claude Code is a CLI tool that understands your codebase.")
;; Claude Code is proprietary software, not end-user redistributable.
(license #f)))
-
-(define claude-dotfiles
- (list "claude/.config/claude/settings.json"
- "claude/.config/claude/CLAUDE.md" "claude/.local/bin/get-api-key"))
-
-(define claude-service
- (service home-program-service-type
- (home-program-configuration (packages (list claude-code))
- (dotfiles claude-dotfiles))))