Skip to content

Update Claude Code

Update Claude Code #39

name: Update Claude Code
on:
workflow_dispatch:
schedule:
# Claude Code ships several builds a week: Anthropic promotes them from the
# `next` tag to `latest` a few days apart. Check hourly so the open PR
# advances within the hour a new `latest` lands. The updater tracks the
# `latest` pointer and reuses one branch, and a no-op run is a Cachix hit,
# so the hourly cadence stays cheap and never stacks new PRs.
- cron: "37 * * * *"
permissions:
contents: read
concurrency:
group: update-claude-code
cancel-in-progress: false
jobs:
update-claude-code:
runs-on: ubuntu-latest
permissions:
contents: write
pull-requests: write
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
# Only the client-side eval knob is needed: `accept-flake-config` lets the
# job consume the flake's indexable-inc Cachix substituter without a
# prompt. No `gccarch-znver5` here (unlike the image checks) because the
# claude-code closure is a fetchurl plus a wrapper and pulls no znver5
# derivation, so claiming a feature the GitHub-hosted runner lacks would
# only risk a misleading build.
- name: Install Determinate Nix
uses: DeterminateSystems/determinate-nix-action@bafaa638b9d5ec0e7e3ac1a7fc80453ef1fd265f # v3
with:
extra-conf: |
accept-flake-config = true
- name: Set up Cachix
uses: cachix/cachix-action@5f2d7c5294214f71b873db4b969586b980625e71 # v17
with:
name: indexable-inc
# Tracks Anthropic's `latest` pointer (no version argument). The updater
# downloads the per-version manifest and its detached GPG signature,
# verifies that signature against the pinned release key in an isolated
# GNUPGHOME, and only then rewrites packages/claude-code/manifest.json. A
# failed signature check exits non-zero and fails this step, so no PR
# opens. Runs from the repo root because the updater writes a relative
# path.
- name: Refresh manifest to the latest signed release
run: nix run .#claude-code.updateScript
# Fail closed on the build too: prove the pinned hash actually fetches and
# the wrapper builds before proposing the bump. Only the x86_64-linux
# binary is built here, but the updater already signature-verified every
# platform's checksum from the same signed manifest, so the darwin and arm
# hashes are trustworthy without a per-platform build. A no-op run is a
# Cachix hit, so this stays cheap when nothing changed.
- name: Build the updated package
run: nix build .#claude-code
- name: Read pinned version
id: manifest
run: echo "version=$(jq -r .version packages/claude-code/manifest.json)" >> "$GITHUB_OUTPUT"
# Opens (or updates) a single PR, and only when manifest.json actually
# changed: create-pull-request no-ops on an empty diff. It reuses the
# `update-claude-code` branch so a daily run advances the same PR.
#
# `token`: a PR opened with the default GITHUB_TOKEN does not trigger the
# `pull_request` workflows (`flake-check`), so branch protection would
# block the merge. Set an `AUTOBUMP_TOKEN` repo secret (a PAT or GitHub
# App token) to make the PR run CI; without it this falls back to
# GITHUB_TOKEN and a maintainer must re-trigger `flake-check` (e.g. close
# and reopen the PR).
- name: Open pull request
uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8.1.1
with:
token: ${{ secrets.AUTOBUMP_TOKEN || github.token }}
base: main
branch: update-claude-code
delete-branch: true
commit-message: "claude-code: bump to ${{ steps.manifest.outputs.version }}"
title: "claude-code: bump to ${{ steps.manifest.outputs.version }}"
body: |
Automated bump of the pinned Claude Code release to `${{ steps.manifest.outputs.version }}`.
`nix run .#claude-code.updateScript` refreshed `packages/claude-code/manifest.json`. The updater verified Anthropic's detached GPG signature against the pinned release key before writing the per-platform SRI hashes, and `nix build .#claude-code` succeeded on `x86_64-linux`, so this fails closed on a bad signature or a broken build.
Auto-bump prototype, made with Claude Opus 4.8.