feat(features): gate Claude Code on the host CPU microarchitecture level

Claude Code runs on Node, whose V8 build requires SSE4.2 and POPCNT
(x86-64-v2). On an older x86_64 CPU it does not run, so it must not be
installed there in the first place.

Nix cannot detect the CPU (pure evaluation, hosts often built elsewhere),
so add features.cpu.microarchLevel: the psABI level a host declares about
itself, defaulting to 2. features.claudeCode.enable derives from it, and
home/claude.nix reads that through home-manager's osConfig and installs
nothing -- CLI, CLAUDE.md, output style or memory symlink -- when it is
off. Hosts without the option (Darwin, the standalone homeConfigurations)
keep the tool enabled.

An assertion fails evaluation if a host force-enables the flag below the
required level, so the mistake surfaces in nix flake check rather than as
an illegal-instruction crash on the machine.
This commit is contained in:
Emma Thorpe
2026-08-17 20:35:29 +01:00
parent 526e6a08e2
commit 0d13581896
5 changed files with 175 additions and 20 deletions
+10 -2
View File
@@ -175,8 +175,16 @@ forced off there) but still runs the agent.
## Claude Code
Managed declaratively by [`claude.nix`](./claude.nix) on every host (the CLI is
`pkgs.claude-code`, tracked to unstable via the flake overlay).
Managed declaratively by [`claude.nix`](./claude.nix) on every host whose CPU
can run it (the CLI is `pkgs.claude-code`, tracked to unstable via the flake
overlay).
**Capability gate.** The module installs nothing — CLI or files — when
`osConfig.features.claudeCode.enable` is off. That flag is derived fleet-wide
from the host's declared CPU level (see "CPU capability gating" in the root
README): the Node runtime needs SSE4.2/POPCNT, so anything below x86-64-v2 (the
Mac Pro 3,1) is excluded. Hosts that do not define the option — the Darwin host
and the standalone `homeConfigurations` — keep it enabled.
| Managed (static, from Nix) | Left mutable (runtime state) |
| --------------------------------------------------- | ------------------------------------------------------ |
+21 -5
View File
@@ -1,4 +1,5 @@
# Claude Code, configured declaratively via home-manager. Wanted on every host.
# Claude Code, configured declaratively via home-manager. Wanted on every host
# whose CPU can run it -- see the gate below.
#
# The STATIC config is managed here: the global CLAUDE.md (persona/context), the
# custom output style, and the auto-memory directory. settings.json is
@@ -10,18 +11,33 @@
# read-only into ~/.claude/memory, so the runtime "save a memory" path no longer
# writes there -- recall still works, but new/changed memories must be added to
# this repo and rebuilt. CLAUDE.md instructs Claude to do exactly that.
{ ... }:
{
lib,
# Set by the NixOS/Darwin home-manager module; absent for the standalone
# homeConfigurations, hence the default.
osConfig ? { },
...
}:
let
# Capability gate, declared once for the whole fleet in modules/features.nix
# (default: on; off on CPUs below x86-64-v2, which cannot run the Node
# runtime Claude Code ships on). Hosts without that option -- the Darwin host
# and the portable standalone profile -- fall back to enabled.
enable = osConfig.features.claudeCode.enable or true;
in
{
programs.claude-code = {
enable = true;
inherit enable;
# package defaults to pkgs.claude-code (tracked to unstable via the flake
# overlay); installs the CLI on every host.
# overlay).
# ~/.claude/CLAUDE.md -- global instructions / persona / memory workflow.
context = ./claude/CLAUDE.md;
};
home.file = {
# Nothing to place when the CLI is not installed: a ~/.claude/memory symlink
# with no Claude Code to read it is just dead state.
home.file = lib.mkIf enable {
# Custom output style. The module has no option for output-styles/, so place
# it directly; selection (settings.json `outputStyle`) stays mutable.
".claude/output-styles/soviet-engineer.md".source = ./claude/output-styles/soviet-engineer.md;