fix(ai-cli): install current upstream Codex and Claude releases

- Pin official Codex 0.161.0 and Claude Code 2.1.292 binaries
- Preserve Codex helpers and Claude's NixOS compatibility wrapper
- Add an updater for stable upstream versions and checksums
- Document CLI updates independently of stable NixOS inputs
This commit is contained in:
clw committed 2026-10-07 18:14:21 +01:00
1 parent 2759278aff
commit 0aac36b7de
6 files changed
+155 -5

No files matched your search

+22 -3
View File
@@ -104,9 +104,28 @@ Studio remains on the ThinkPads as an existing IDE/workflow choice, but its
bundled SDK is worth reviewing if moving fully to project-local tooling. bundled SDK is worth reviewing if moving fully to project-local tooling.
Codex CLI (`codex`) and Claude Code (`claude`) are installed on every host. Codex CLI (`codex`) and Claude Code (`claude`) are installed on every host.
Run each command as your normal user and follow its sign-in flow. Update these Run each command as your normal user and follow its sign-in flow.
Nix-managed packages through the flake and rebuild rather than their standalone They use official upstream binaries, with versions and SHA-256 checksums pinned
installers. Authentication stays in your home directory, outside this repo. in `packages/ai-cli-versions.json`, independently of the stable NixOS release.
`packages/ai-cli.nix` packages Codex and reuses Nixpkgs' Claude Code wrapper.
Authentication stays in your home directory, outside this repo.
To update both to the latest stable upstream releases, run from this repo:
```sh
nix shell nixpkgs#python3 -c python3 scripts/update-ai-cli.py
git diff -- packages/ai-cli-versions.json
nix build .#codex .#claude-code --no-link
sudo nixos-rebuild switch --flake .#framework
codex --version
claude --version
git add packages/ai-cli-versions.json
git commit -m "chore(ai-cli): update upstream releases"
```
Use your own host name when rebuilding. The updater changes only the CLI pins;
`nix flake update` updates NixOS inputs but does not update these pins.
Updates remain explicit and reproducible; no upstream self-updaters are used.
The official ChatGPT Linux desktop preview does not formally support NixOS. The official ChatGPT Linux desktop preview does not formally support NixOS.
Our pinned `chatgpt` package supports macOS only, so it is not installed here. Our pinned `chatgpt` package supports macOS only, so it is not installed here.
+8
View File
@@ -19,6 +19,14 @@
... ...
}: }:
{ {
# Build the CLI packages separately before switching a host.
packages.x86_64-linux = import ./packages/ai-cli.nix {
pkgs = import nixpkgs {
system = "x86_64-linux";
config.allowUnfree = true;
};
};
nixosConfigurations = { nixosConfigurations = {
t450s = nixpkgs.lib.nixosSystem { t450s = nixpkgs.lib.nixosSystem {
specialArgs.unstable = import nixpkgs-unstable { specialArgs.unstable = import nixpkgs-unstable {
+5 -2
View File
@@ -1,4 +1,7 @@
{ pkgs, ... }: { pkgs, ... }:
let
aiCli = import ../packages/ai-cli.nix { inherit pkgs; };
in
{ {
# Workstation tools only; language runtimes and SDKs belong in projects. # Workstation tools only; language runtimes and SDKs belong in projects.
environment.systemPackages = with pkgs; [ environment.systemPackages = with pkgs; [
@@ -19,8 +22,8 @@
zip zip
# Coding assistants; authenticate separately as your normal user. # Coding assistants; authenticate separately as your normal user.
codex aiCli.codex
claude-code aiCli.claude-code
# Infrastructure, containers, and structured data. # Infrastructure, containers, and structured data.
ansible ansible
+10
View File
@@ -0,0 +1,10 @@
{
"codex": {
"version": "0.161.0",
"sha256": "04d8ab9dbcb9df0edf3c67dca5072a374babfdf762a9bc4ae649ae140b8e2cf0"
},
"claude-code": {
"version": "2.1.292",
"sha256": "a967e7b1d8b4e47ee421d5433027880347952b0c0857abf880e2c942a4ec93b3"
}
}
+53
View File
@@ -0,0 +1,53 @@
{ pkgs }:
let
versions = builtins.fromJSON (builtins.readFile ./ai-cli-versions.json);
in
{
# Use the complete official package, including Code Mode and voice helpers.
codex = pkgs.stdenvNoCC.mkDerivation {
pname = "codex";
inherit (versions.codex) version;
src = pkgs.fetchurl {
url = "https://github.com/openai/codex/releases/download/rust-v${versions.codex.version}/codex-package-x86_64-unknown-linux-musl.tar.gz";
inherit (versions.codex) sha256;
};
sourceRoot = ".";
nativeBuildInputs = [ pkgs.autoPatchelfHook pkgs.makeWrapper ];
buildInputs = [ pkgs.glibc pkgs.ncurses ];
dontBuild = true;
dontStrip = true;
installPhase = ''
runHook preInstall
mkdir -p "$out"
cp -r bin codex-path codex-resources codex-package.json "$out/"
runHook postInstall
'';
postFixup = ''
wrapProgram "$out/bin/codex" \
--prefix PATH : ${pkgs.lib.makeBinPath [ pkgs.ripgrep pkgs.bubblewrap ]}
'';
doInstallCheck = true;
installCheckPhase = ''
runHook preInstallCheck
"$out/bin/codex" --version | grep -F "codex-cli ${versions.codex.version}"
runHook postInstallCheck
'';
meta = {
description = "OpenAI coding agent, official upstream binary package";
homepage = "https://github.com/openai/codex";
license = pkgs.lib.licenses.asl20;
mainProgram = "codex";
platforms = [ "x86_64-linux" ];
sourceProvenance = [ pkgs.lib.sourceTypes.binaryNativeCode ];
};
};
# Keep Nixpkgs' NixOS compatibility fixes and sandbox dependencies.
claude-code = pkgs.claude-code.override {
manifest = {
inherit (versions.claude-code) version;
platforms.linux-x64.checksum = versions.claude-code.sha256;
};
};
}
+57
View File
@@ -0,0 +1,57 @@
#!/usr/bin/env python3
"""Pin the latest stable upstream CLI releases without updating NixOS."""
import json
import re
import urllib.request
from pathlib import Path
def fetch_json(url):
request = urllib.request.Request(
url, headers={"User-Agent": "nixos-config-ai-cli-updater"}
)
with urllib.request.urlopen(request, timeout=60) as response:
return json.load(response)
def latest_release(repository):
release = fetch_json(f"https://api.github.com/repos/{repository}/releases/latest")
if release["prerelease"] or release["draft"]:
raise ValueError(f"Expected a stable release from {repository}")
return release
codex = latest_release("openai/codex")
codex_version = codex["tag_name"].removeprefix("rust-v")
claude = latest_release("anthropics/claude-code")
claude_version = claude["tag_name"].removeprefix("v")
for version in (codex_version, claude_version):
if not re.fullmatch(r"\d+\.\d+\.\d+", version):
raise ValueError(f"Unexpected release version: {version}")
asset = next(
item for item in codex["assets"]
if item["name"] == "codex-package-x86_64-unknown-linux-musl.tar.gz"
)
digest = asset["digest"]
if not re.fullmatch(r"sha256:[0-9a-f]{64}", digest):
raise ValueError("Codex release asset has no valid SHA-256 digest")
manifest = fetch_json(
f"https://downloads.claude.ai/claude-code-releases/{claude_version}/manifest.json"
)
if manifest["version"] != claude_version:
raise ValueError("Claude release and download manifest versions differ")
claude_hash = manifest["platforms"]["linux-x64"]["checksum"]
if not re.fullmatch(r"[0-9a-f]{64}", claude_hash):
raise ValueError("Claude manifest has no valid SHA-256 checksum")
versions = {
"codex": {"version": codex_version, "sha256": digest.removeprefix("sha256:")},
"claude-code": {"version": claude_version, "sha256": claude_hash},
}
destination = Path(__file__).resolve().parent.parent / "packages" / "ai-cli-versions.json"
destination.write_text(json.dumps(versions, indent=2) + "\n")
print(f"Pinned Codex {codex_version} and Claude Code {claude_version}.")
print("Review the diff, build the packages, rebuild your host and commit the pins.")