From cd32cc70fa5610cb5c04c81639c8903dcd23feb3 Mon Sep 17 00:00:00 2001 From: Nicholas Romero Date: Thu, 22 Jan 2026 11:52:35 -0600 Subject: [PATCH 1/4] feat: build claude-code from source for version control - Add local claude-code package in nix/claude-code/ - Mirror upstream nixpkgs package definition - Add update.sh script to sync from nixpkgs or npm - Add nix/update command for easy dependency updates - Add GitHub Action to auto-create PRs for new versions This allows staying current with claude-code releases independent of nixpkgs update cycles. Co-Authored-By: Claude Opus 4.5 --- .github/workflows/update-claude-code.yml | 91 +++++++ flake.lock | 6 +- flake.nix | 10 +- nix/claude-code/package-lock.json | 314 +++++++++++++++++++++++ nix/claude-code/package.nix | 78 ++++++ nix/claude-code/update.sh | 137 ++++++++++ nix/update | 18 ++ 7 files changed, 649 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/update-claude-code.yml create mode 100644 nix/claude-code/package-lock.json create mode 100644 nix/claude-code/package.nix create mode 100755 nix/claude-code/update.sh create mode 100755 nix/update diff --git a/.github/workflows/update-claude-code.yml b/.github/workflows/update-claude-code.yml new file mode 100644 index 00000000..6d61ccd3 --- /dev/null +++ b/.github/workflows/update-claude-code.yml @@ -0,0 +1,91 @@ +name: Update Claude Code + +on: + schedule: + # Run daily at 6 AM UTC + - cron: '0 6 * * *' + workflow_dispatch: # Allow manual trigger + +jobs: + update: + runs-on: ubuntu-latest + permissions: + contents: write + pull-requests: write + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Install Nix + uses: cachix/install-nix-action@v30 + with: + nix_path: nixpkgs=channel:nixos-unstable + extra_nix_config: | + experimental-features = nix-command flakes + + - name: Check for new claude-code version + id: check-version + run: | + # Get current version from package.nix + CURRENT_VERSION=$(grep 'version = "' nix/claude-code/package.nix | head -1 | sed 's/.*version = "\([^"]*\)".*/\1/') + echo "current_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT + + # Get latest version from npm + LATEST_VERSION=$(npm view @anthropic-ai/claude-code version 2>/dev/null || echo "") + echo "latest_version=$LATEST_VERSION" >> $GITHUB_OUTPUT + + # Check if update is needed + if [ -z "$LATEST_VERSION" ]; then + echo "Failed to fetch latest version from npm" + echo "needs_update=false" >> $GITHUB_OUTPUT + elif [ "$CURRENT_VERSION" = "$LATEST_VERSION" ]; then + echo "Already at latest version: $CURRENT_VERSION" + echo "needs_update=false" >> $GITHUB_OUTPUT + else + echo "Update available: $CURRENT_VERSION -> $LATEST_VERSION" + echo "needs_update=true" >> $GITHUB_OUTPUT + fi + + - name: Update claude-code package + if: steps.check-version.outputs.needs_update == 'true' + run: | + # Run the update script (syncs from nixpkgs) + ./nix/claude-code/update.sh + + - name: Update flake.lock + if: steps.check-version.outputs.needs_update == 'true' + run: | + nix flake update + + - name: Verify build + if: steps.check-version.outputs.needs_update == 'true' + run: | + # Test that the package builds successfully + nix develop --command claude --version + + - name: Create Pull Request + if: steps.check-version.outputs.needs_update == 'true' + uses: peter-evans/create-pull-request@v7 + with: + token: ${{ secrets.GITHUB_TOKEN }} + commit-message: "chore(deps): update claude-code to ${{ steps.check-version.outputs.latest_version }}" + title: "chore(deps): update claude-code to ${{ steps.check-version.outputs.latest_version }}" + body: | + Automated update of claude-code package. + + **Changes:** + - claude-code: ${{ steps.check-version.outputs.current_version }} → ${{ steps.check-version.outputs.latest_version }} + - Updated flake.lock + + **Verification:** + - Package builds successfully + - `claude --version` returns expected version + + --- + *This PR was automatically created by the update-claude-code workflow.* + branch: update-claude-code + delete-branch: true + labels: | + dependencies + automated diff --git a/flake.lock b/flake.lock index 5a9df819..ce228ff8 100644 --- a/flake.lock +++ b/flake.lock @@ -20,11 +20,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1768564909, - "narHash": "sha256-Kell/SpJYVkHWMvnhqJz/8DqQg2b6PguxVWOuadbHCc=", + "lastModified": 1769018530, + "narHash": "sha256-MJ27Cy2NtBEV5tsK+YraYr2g851f3Fl1LpNHDzDX15c=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "e4bae1bd10c9c57b2cf517953ab70060a828ee6f", + "rev": "88d3861acdd3d2f0e361767018218e51810df8a1", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index e2367a1f..d2218afb 100644 --- a/flake.nix +++ b/flake.nix @@ -14,6 +14,8 @@ # Allow unfree packages to support the Business Source License 1.1 config.allowUnfree = true; }; + # Local claude-code package for version control (update via nix/claude-code/update.sh) + claude-code = pkgs.callPackage ./nix/claude-code/package.nix { }; # Read version from pyproject.toml to avoid duplication pyproject = builtins.fromTOML (builtins.readFile ./pyproject.toml); deepwork = pkgs.python311Packages.buildPythonPackage { @@ -44,8 +46,8 @@ # System tools jq # For JSON processing - # CLI tools - claude-code # Claude Code CLI + # CLI tools (claude-code is locally built, see nix/claude-code/) + claude-code gh # GitHub CLI ]; @@ -79,6 +81,9 @@ # Set PYTHONPATH for editable install access to src/ export PYTHONPATH="$PWD/src:$PYTHONPATH" + # Add nix/ scripts to PATH (for 'update' command) + export PATH="$PWD/nix:$PATH" + # Only show welcome message in interactive shells if [[ $- == *i* ]]; then echo "" @@ -94,6 +99,7 @@ echo " mypy src/ Type check" echo " claude-code Claude Code CLI" echo " gh GitHub CLI" + echo " update Update claude-code and flake inputs" echo "" fi ''; diff --git a/nix/claude-code/package-lock.json b/nix/claude-code/package-lock.json new file mode 100644 index 00000000..f9766e4c --- /dev/null +++ b/nix/claude-code/package-lock.json @@ -0,0 +1,314 @@ +{ + "name": "@anthropic-ai/claude-code", + "version": "2.1.15", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@anthropic-ai/claude-code", + "version": "2.1.15", + "license": "SEE LICENSE IN README.md", + "bin": { + "claude": "cli.js" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "^0.33.5", + "@img/sharp-darwin-x64": "^0.33.5", + "@img/sharp-linux-arm": "^0.33.5", + "@img/sharp-linux-arm64": "^0.33.5", + "@img/sharp-linux-x64": "^0.33.5", + "@img/sharp-linuxmusl-arm64": "^0.33.5", + "@img/sharp-linuxmusl-x64": "^0.33.5", + "@img/sharp-win32-x64": "^0.33.5" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", + "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", + "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.5" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + } + } +} diff --git a/nix/claude-code/package.nix b/nix/claude-code/package.nix new file mode 100644 index 00000000..053d1204 --- /dev/null +++ b/nix/claude-code/package.nix @@ -0,0 +1,78 @@ +# Claude Code package - locally maintained for version control +# Based on nixpkgs: https://github.com/NixOS/nixpkgs/tree/master/pkgs/by-name/cl/claude-code +# +# To update: Run ./update.sh from this directory +{ + lib, + stdenv, + buildNpmPackage, + fetchzip, + versionCheckHook, + writableTmpDirAsHomeHook, + bubblewrap, + procps, + socat, +}: +buildNpmPackage (finalAttrs: { + pname = "claude-code"; + version = "2.1.15"; + + src = fetchzip { + url = "https://registry.npmjs.org/@anthropic-ai/claude-code/-/claude-code-${finalAttrs.version}.tgz"; + hash = "sha256-3zhjeAwKj1fMLuriX1qpVA8zaCk1oekJ1UmeEdDx4Xg="; + }; + + npmDepsHash = "sha256-K5re0co3Tkz5peXHe/UUlsqAWq4YzSULdY9+xncfL5A="; + + strictDeps = true; + + postPatch = '' + cp ${./package-lock.json} package-lock.json + + # Replace hardcoded `/bin/bash` with `/usr/bin/env bash` for Nix compatibility + # https://github.com/anthropics/claude-code/issues/15195 + substituteInPlace cli.js \ + --replace-warn '#!/bin/bash' '#!/usr/bin/env bash' + ''; + + dontNpmBuild = true; + + env.AUTHORIZED = "1"; + + # `claude-code` tries to auto-update by default, this disables that functionality. + # https://docs.anthropic.com/en/docs/agents-and-tools/claude-code/overview#environment-variables + # The DEV=true env var causes claude to crash with `TypeError: window.WebSocket is not a constructor` + postInstall = '' + wrapProgram $out/bin/claude \ + --set DISABLE_AUTOUPDATER 1 \ + --unset DEV \ + --prefix PATH : ${ + lib.makeBinPath ( + [ + # claude-code uses [node-tree-kill](https://github.com/pkrumins/node-tree-kill) which requires procps's pgrep(darwin) or ps(linux) + procps + ] + # the following packages are required for the sandbox to work (Linux only) + ++ lib.optionals stdenv.hostPlatform.isLinux [ + bubblewrap + socat + ] + ) + } + ''; + + doInstallCheck = true; + nativeInstallCheckInputs = [ + writableTmpDirAsHomeHook + versionCheckHook + ]; + versionCheckKeepEnvironment = [ "HOME" ]; + + meta = { + description = "Agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster"; + homepage = "https://github.com/anthropics/claude-code"; + downloadPage = "https://www.npmjs.com/package/@anthropic-ai/claude-code"; + license = lib.licenses.unfree; + mainProgram = "claude"; + }; +}) diff --git a/nix/claude-code/update.sh b/nix/claude-code/update.sh new file mode 100755 index 00000000..13ff696c --- /dev/null +++ b/nix/claude-code/update.sh @@ -0,0 +1,137 @@ +#!/usr/bin/env bash +# Update claude-code package to latest version +# +# Usage: +# ./update.sh # Sync from nixpkgs (recommended) +# ./update.sh --manual # Manual update from npm + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +sync_from_nixpkgs() { + echo "Syncing claude-code package from nixpkgs..." + + NIXPKGS_URL="https://raw.githubusercontent.com/NixOS/nixpkgs/master/pkgs/by-name/cl/claude-code" + + # Fetch package.nix + echo "Fetching package.nix..." + curl -sL "$NIXPKGS_URL/package.nix" -o package.nix.new + + # Fetch package-lock.json + echo "Fetching package-lock.json..." + curl -sL "$NIXPKGS_URL/package-lock.json" -o package-lock.json.new + + # Extract version from new package.nix + NEW_VERSION=$(grep 'version = "' package.nix.new | head -1 | sed 's/.*version = "\([^"]*\)".*/\1/') + OLD_VERSION=$(grep 'version = "' package.nix | head -1 | sed 's/.*version = "\([^"]*\)".*/\1/') + + if [[ "$NEW_VERSION" == "$OLD_VERSION" ]]; then + echo "Already at latest version: $OLD_VERSION" + rm -f package.nix.new package-lock.json.new + exit 0 + fi + + echo "Updating from $OLD_VERSION -> $NEW_VERSION" + + # Replace files, keeping our local header comment + { + echo "# Claude Code package - locally maintained for version control" + echo "# Based on nixpkgs: https://github.com/NixOS/nixpkgs/tree/master/pkgs/by-name/cl/claude-code" + echo "#" + echo "# To update: Run ./update.sh from this directory" + # Skip the nixpkgs-specific comment at the top and keep the rest + tail -n +7 package.nix.new | head -n -3 # Skip comment and maintainers + echo " meta = {" + echo " description = \"Agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster\";" + echo " homepage = \"https://github.com/anthropics/claude-code\";" + echo " downloadPage = \"https://www.npmjs.com/package/@anthropic-ai/claude-code\";" + echo " license = lib.licenses.unfree;" + echo " mainProgram = \"claude\";" + echo " };" + echo "})" + } > package.nix + + mv package-lock.json.new package-lock.json + rm -f package.nix.new + + echo "Updated to version $NEW_VERSION" + echo "" + echo "Next steps:" + echo " 1. Test: nix develop (from repo root)" + echo " 2. Verify: claude --version" + echo " 3. Commit the changes" +} + +manual_update() { + echo "Manual update from npm..." + + # Get latest version from npm + echo "Fetching latest version from npm..." + VERSION=$(npm view @anthropic-ai/claude-code version 2>/dev/null) + OLD_VERSION=$(grep 'version = "' package.nix | head -1 | sed 's/.*version = "\([^"]*\)".*/\1/') + + if [[ "$VERSION" == "$OLD_VERSION" ]]; then + echo "Already at latest version: $OLD_VERSION" + exit 0 + fi + + echo "Updating from $OLD_VERSION -> $VERSION" + + # Download and compute hash + TARBALL_URL="https://registry.npmjs.org/@anthropic-ai/claude-code/-/claude-code-${VERSION}.tgz" + TMPDIR=$(mktemp -d) + trap "rm -rf $TMPDIR" EXIT + + echo "Downloading tarball..." + curl -sL "$TARBALL_URL" -o "$TMPDIR/claude-code.tgz" + + echo "Computing source hash..." + # Unpack and compute nix hash + mkdir -p "$TMPDIR/src" + tar -xzf "$TMPDIR/claude-code.tgz" -C "$TMPDIR/src" --strip-components=1 + SRC_HASH=$(nix hash path "$TMPDIR/src" 2>/dev/null || nix-hash --type sha256 --base32 "$TMPDIR/src") + + # Extract package-lock.json from tarball if it exists + if [[ -f "$TMPDIR/src/package-lock.json" ]]; then + cp "$TMPDIR/src/package-lock.json" package-lock.json + echo "Updated package-lock.json from tarball" + else + echo "Warning: No package-lock.json in tarball, fetching from nixpkgs..." + curl -sL "https://raw.githubusercontent.com/NixOS/nixpkgs/master/pkgs/by-name/cl/claude-code/package-lock.json" -o package-lock.json + fi + + # Update version and hash in package.nix + sed -i "s/version = \"[^\"]*\"/version = \"$VERSION\"/" package.nix + sed -i "s|hash = \"sha256-[^\"]*\"|hash = \"$SRC_HASH\"|" package.nix + + echo "" + echo "Updated to version $VERSION" + echo "Source hash: $SRC_HASH" + echo "" + echo "NOTE: You may need to update npmDepsHash manually." + echo "Run 'nix develop' - if it fails with hash mismatch, update npmDepsHash with the correct value from the error." + echo "" + echo "Next steps:" + echo " 1. Test: nix develop (from repo root)" + echo " 2. Verify: claude --version" + echo " 3. Commit the changes" +} + +# Parse arguments +case "${1:-}" in + --manual) + manual_update + ;; + --help|-h) + echo "Usage: $0 [--manual]" + echo "" + echo "Options:" + echo " (default) Sync package from nixpkgs (recommended)" + echo " --manual Update directly from npm (requires computing hashes)" + ;; + *) + sync_from_nixpkgs + ;; +esac diff --git a/nix/update b/nix/update new file mode 100755 index 00000000..95057b45 --- /dev/null +++ b/nix/update @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +# Update all Nix dependencies (claude-code package and flake inputs) +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + +cd "$REPO_ROOT" + +echo "Updating claude-code package..." +"$SCRIPT_DIR/claude-code/update.sh" + +echo "" +echo "Updating flake inputs..." +nix flake update + +echo "" +echo "Done! Run 'nix develop' to reload the environment." From 915628a8677816198ae7f1ba2798631288e7634f Mon Sep 17 00:00:00 2001 From: Noah Horton Date: Thu, 22 Jan 2026 11:12:16 -0700 Subject: [PATCH 2/4] Updated docs --- CONTRIBUTING.md | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 85e8994d..b8cbe388 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -145,11 +145,13 @@ The Nix environment provides: | Tool | Description | |------|-------------| | `deepwork` | CLI using your local source code (editable install) | +| `claude` | Claude Code CLI (built from source for version control) | | `pytest` | Test runner with all plugins | | `ruff` | Fast Python linter and formatter | | `mypy` | Static type checker | | `uv` | Python package manager | | `python` | Python 3.11 interpreter | +| `update` | Updates claude-code and flake inputs | #### CI Usage @@ -161,6 +163,27 @@ nix develop --command ruff check src/ nix develop --command mypy src/ ``` +#### Updating Development Dependencies + +The Nix environment includes Claude Code built from source to ensure version control (the nixpkgs version can lag behind npm releases). Use the `update` command to keep dependencies current: + +```bash +# In the dev shell - updates claude-code and flake inputs +update +``` + +For manual control over Claude Code updates: + +```bash +# Sync from nixpkgs (default) +./nix/claude-code/update.sh + +# Update directly from npm (when nixpkgs lags behind) +./nix/claude-code/update.sh --manual +``` + +A GitHub Action automatically checks for new Claude Code versions daily and creates PRs when updates are available. + ### Option 2: Manual Setup (Without Nix) If you prefer not to use Nix: From 572f6b17e2db105f4172a16f8c678a78d2307e1e Mon Sep 17 00:00:00 2001 From: Nicholas Romero Date: Thu, 22 Jan 2026 12:41:03 -0600 Subject: [PATCH 3/4] refactor: simplify claude-code updates to always use npm - Rewrite update.sh to fetch directly from npm and compute hashes automatically using prefetch-npm-deps - Remove nixpkgs sync option (--manual flag removed) - Simplify GitHub workflow by letting the script handle version checking - Update CONTRIBUTING.md to reflect the simplified process Co-Authored-By: Claude Opus 4.5 --- .github/workflows/update-claude-code.yml | 47 ++----- CONTRIBUTING.md | 8 +- nix/claude-code/update.sh | 156 +++++------------------ 3 files changed, 48 insertions(+), 163 deletions(-) diff --git a/.github/workflows/update-claude-code.yml b/.github/workflows/update-claude-code.yml index 6d61ccd3..2080fe95 100644 --- a/.github/workflows/update-claude-code.yml +++ b/.github/workflows/update-claude-code.yml @@ -24,58 +24,33 @@ jobs: extra_nix_config: | experimental-features = nix-command flakes - - name: Check for new claude-code version - id: check-version - run: | - # Get current version from package.nix - CURRENT_VERSION=$(grep 'version = "' nix/claude-code/package.nix | head -1 | sed 's/.*version = "\([^"]*\)".*/\1/') - echo "current_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT - - # Get latest version from npm - LATEST_VERSION=$(npm view @anthropic-ai/claude-code version 2>/dev/null || echo "") - echo "latest_version=$LATEST_VERSION" >> $GITHUB_OUTPUT - - # Check if update is needed - if [ -z "$LATEST_VERSION" ]; then - echo "Failed to fetch latest version from npm" - echo "needs_update=false" >> $GITHUB_OUTPUT - elif [ "$CURRENT_VERSION" = "$LATEST_VERSION" ]; then - echo "Already at latest version: $CURRENT_VERSION" - echo "needs_update=false" >> $GITHUB_OUTPUT - else - echo "Update available: $CURRENT_VERSION -> $LATEST_VERSION" - echo "needs_update=true" >> $GITHUB_OUTPUT - fi - - name: Update claude-code package - if: steps.check-version.outputs.needs_update == 'true' + id: update run: | - # Run the update script (syncs from nixpkgs) + # Script exits 0 if already at latest (no changes to commit) ./nix/claude-code/update.sh + # Capture version for PR title + VERSION=$(grep 'version = "' nix/claude-code/package.nix | head -1 | sed 's/.*version = "\([^"]*\)".*/\1/') + echo "version=$VERSION" >> $GITHUB_OUTPUT + - name: Update flake.lock - if: steps.check-version.outputs.needs_update == 'true' - run: | - nix flake update + run: nix flake update - name: Verify build - if: steps.check-version.outputs.needs_update == 'true' - run: | - # Test that the package builds successfully - nix develop --command claude --version + run: nix develop --command claude --version - name: Create Pull Request - if: steps.check-version.outputs.needs_update == 'true' uses: peter-evans/create-pull-request@v7 with: token: ${{ secrets.GITHUB_TOKEN }} - commit-message: "chore(deps): update claude-code to ${{ steps.check-version.outputs.latest_version }}" - title: "chore(deps): update claude-code to ${{ steps.check-version.outputs.latest_version }}" + commit-message: "chore(deps): update claude-code to ${{ steps.update.outputs.version }}" + title: "chore(deps): update claude-code to ${{ steps.update.outputs.version }}" body: | Automated update of claude-code package. **Changes:** - - claude-code: ${{ steps.check-version.outputs.current_version }} → ${{ steps.check-version.outputs.latest_version }} + - claude-code updated to ${{ steps.update.outputs.version }} - Updated flake.lock **Verification:** diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b8cbe388..3a4b5cdb 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -172,16 +172,14 @@ The Nix environment includes Claude Code built from source to ensure version con update ``` -For manual control over Claude Code updates: +To manually update Claude Code: ```bash -# Sync from nixpkgs (default) ./nix/claude-code/update.sh - -# Update directly from npm (when nixpkgs lags behind) -./nix/claude-code/update.sh --manual ``` +This fetches the latest version from npm, computes the necessary hashes using `prefetch-npm-deps`, and updates `package.nix` automatically. + A GitHub Action automatically checks for new Claude Code versions daily and creates PRs when updates are available. ### Option 2: Manual Setup (Without Nix) diff --git a/nix/claude-code/update.sh b/nix/claude-code/update.sh index 13ff696c..cfc648bc 100755 --- a/nix/claude-code/update.sh +++ b/nix/claude-code/update.sh @@ -1,137 +1,49 @@ #!/usr/bin/env bash -# Update claude-code package to latest version -# -# Usage: -# ./update.sh # Sync from nixpkgs (recommended) -# ./update.sh --manual # Manual update from npm - +# Update claude-code package to latest npm version set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" cd "$SCRIPT_DIR" -sync_from_nixpkgs() { - echo "Syncing claude-code package from nixpkgs..." - - NIXPKGS_URL="https://raw.githubusercontent.com/NixOS/nixpkgs/master/pkgs/by-name/cl/claude-code" - - # Fetch package.nix - echo "Fetching package.nix..." - curl -sL "$NIXPKGS_URL/package.nix" -o package.nix.new - - # Fetch package-lock.json - echo "Fetching package-lock.json..." - curl -sL "$NIXPKGS_URL/package-lock.json" -o package-lock.json.new - - # Extract version from new package.nix - NEW_VERSION=$(grep 'version = "' package.nix.new | head -1 | sed 's/.*version = "\([^"]*\)".*/\1/') - OLD_VERSION=$(grep 'version = "' package.nix | head -1 | sed 's/.*version = "\([^"]*\)".*/\1/') - - if [[ "$NEW_VERSION" == "$OLD_VERSION" ]]; then - echo "Already at latest version: $OLD_VERSION" - rm -f package.nix.new package-lock.json.new - exit 0 - fi - - echo "Updating from $OLD_VERSION -> $NEW_VERSION" - - # Replace files, keeping our local header comment - { - echo "# Claude Code package - locally maintained for version control" - echo "# Based on nixpkgs: https://github.com/NixOS/nixpkgs/tree/master/pkgs/by-name/cl/claude-code" - echo "#" - echo "# To update: Run ./update.sh from this directory" - # Skip the nixpkgs-specific comment at the top and keep the rest - tail -n +7 package.nix.new | head -n -3 # Skip comment and maintainers - echo " meta = {" - echo " description = \"Agentic coding tool that lives in your terminal, understands your codebase, and helps you code faster\";" - echo " homepage = \"https://github.com/anthropics/claude-code\";" - echo " downloadPage = \"https://www.npmjs.com/package/@anthropic-ai/claude-code\";" - echo " license = lib.licenses.unfree;" - echo " mainProgram = \"claude\";" - echo " };" - echo "})" - } > package.nix - - mv package-lock.json.new package-lock.json - rm -f package.nix.new - - echo "Updated to version $NEW_VERSION" - echo "" - echo "Next steps:" - echo " 1. Test: nix develop (from repo root)" - echo " 2. Verify: claude --version" - echo " 3. Commit the changes" -} - -manual_update() { - echo "Manual update from npm..." - - # Get latest version from npm - echo "Fetching latest version from npm..." - VERSION=$(npm view @anthropic-ai/claude-code version 2>/dev/null) - OLD_VERSION=$(grep 'version = "' package.nix | head -1 | sed 's/.*version = "\([^"]*\)".*/\1/') +# Get versions +OLD_VERSION=$(grep 'version = "' package.nix | head -1 | sed 's/.*version = "\([^"]*\)".*/\1/') +VERSION=$(npm view @anthropic-ai/claude-code version 2>/dev/null) - if [[ "$VERSION" == "$OLD_VERSION" ]]; then - echo "Already at latest version: $OLD_VERSION" - exit 0 - fi +if [[ "$VERSION" == "$OLD_VERSION" ]]; then + echo "Already at latest version: $OLD_VERSION" + exit 0 +fi - echo "Updating from $OLD_VERSION -> $VERSION" +echo "Updating claude-code: $OLD_VERSION -> $VERSION" - # Download and compute hash - TARBALL_URL="https://registry.npmjs.org/@anthropic-ai/claude-code/-/claude-code-${VERSION}.tgz" - TMPDIR=$(mktemp -d) - trap "rm -rf $TMPDIR" EXIT +# Download tarball +TARBALL_URL="https://registry.npmjs.org/@anthropic-ai/claude-code/-/claude-code-${VERSION}.tgz" +TMPDIR=$(mktemp -d) +trap "rm -rf $TMPDIR" EXIT - echo "Downloading tarball..." - curl -sL "$TARBALL_URL" -o "$TMPDIR/claude-code.tgz" +curl -sL "$TARBALL_URL" -o "$TMPDIR/claude-code.tgz" - echo "Computing source hash..." - # Unpack and compute nix hash - mkdir -p "$TMPDIR/src" - tar -xzf "$TMPDIR/claude-code.tgz" -C "$TMPDIR/src" --strip-components=1 - SRC_HASH=$(nix hash path "$TMPDIR/src" 2>/dev/null || nix-hash --type sha256 --base32 "$TMPDIR/src") +# Extract and compute source hash +mkdir -p "$TMPDIR/src" +tar -xzf "$TMPDIR/claude-code.tgz" -C "$TMPDIR/src" --strip-components=1 +SRC_HASH=$(nix hash path "$TMPDIR/src") - # Extract package-lock.json from tarball if it exists - if [[ -f "$TMPDIR/src/package-lock.json" ]]; then - cp "$TMPDIR/src/package-lock.json" package-lock.json - echo "Updated package-lock.json from tarball" - else - echo "Warning: No package-lock.json in tarball, fetching from nixpkgs..." - curl -sL "https://raw.githubusercontent.com/NixOS/nixpkgs/master/pkgs/by-name/cl/claude-code/package-lock.json" -o package-lock.json - fi +# Get package-lock.json from tarball +if [[ -f "$TMPDIR/src/package-lock.json" ]]; then + cp "$TMPDIR/src/package-lock.json" package-lock.json +else + echo "Error: No package-lock.json in tarball" + exit 1 +fi - # Update version and hash in package.nix - sed -i "s/version = \"[^\"]*\"/version = \"$VERSION\"/" package.nix - sed -i "s|hash = \"sha256-[^\"]*\"|hash = \"$SRC_HASH\"|" package.nix +# Compute npmDepsHash using prefetch-npm-deps +NPM_DEPS_HASH=$(nix shell nixpkgs#prefetch-npm-deps -c prefetch-npm-deps package-lock.json 2>/dev/null) - echo "" - echo "Updated to version $VERSION" - echo "Source hash: $SRC_HASH" - echo "" - echo "NOTE: You may need to update npmDepsHash manually." - echo "Run 'nix develop' - if it fails with hash mismatch, update npmDepsHash with the correct value from the error." - echo "" - echo "Next steps:" - echo " 1. Test: nix develop (from repo root)" - echo " 2. Verify: claude --version" - echo " 3. Commit the changes" -} +# Update package.nix +sed -i "s/version = \"[^\"]*\"/version = \"$VERSION\"/" package.nix +sed -i "s|hash = \"sha256-[^\"]*\"|hash = \"$SRC_HASH\"|" package.nix +sed -i "s|npmDepsHash = \"sha256-[^\"]*\"|npmDepsHash = \"$NPM_DEPS_HASH\"|" package.nix -# Parse arguments -case "${1:-}" in - --manual) - manual_update - ;; - --help|-h) - echo "Usage: $0 [--manual]" - echo "" - echo "Options:" - echo " (default) Sync package from nixpkgs (recommended)" - echo " --manual Update directly from npm (requires computing hashes)" - ;; - *) - sync_from_nixpkgs - ;; -esac +echo "Updated to version $VERSION" +echo " Source hash: $SRC_HASH" +echo " Deps hash: $NPM_DEPS_HASH" From 785b6a3c263e11ac50e02eb052875c7fc4fbf490 Mon Sep 17 00:00:00 2001 From: Nicholas Romero Date: Thu, 22 Jan 2026 13:00:12 -0600 Subject: [PATCH 4/4] chore: remove labels from update-claude-code workflow Co-Authored-By: Claude Opus 4.5 --- .github/workflows/update-claude-code.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/update-claude-code.yml b/.github/workflows/update-claude-code.yml index 2080fe95..99dbbf2c 100644 --- a/.github/workflows/update-claude-code.yml +++ b/.github/workflows/update-claude-code.yml @@ -61,6 +61,3 @@ jobs: *This PR was automatically created by the update-claude-code workflow.* branch: update-claude-code delete-branch: true - labels: | - dependencies - automated