This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Educational project: explore and understand Nix internals through readable Python. Each module reimplements one Nix concept (base32, NAR, store paths, derivations, daemon protocol) in straightforward Python, verified against the real nix CLI.
Priority: readability over performance. Comments should explain why Nix does things a certain way, not just what the code does. When modifying code, preserve or improve the educational value.
nix develop # Python 3, pytest, mkdocs, Nix C headers
python -m pix hash-path <path> [--base32] # NAR hash
python -m pix store-path <path> [--name NAME] # compute store path
python -m pix drv-show <drv-path> # parse .drv as JSON
python -m pix path-info <store-path> # query daemon
python -m pix is-valid <store-path> # check path validity
python -m pix add-text <name> [content] # add text to store
python -m pix build <path>... # build via daemon
pytest tests/ pixpkgs/tests/ -v # ~92 tests
mkdocs serve # docs at localhost:8000
pix/base32.py— Nix base32: custom alphabet, reversed bit extraction vs RFC 4648pix/hash.py— XOR-fold compression (32→20 bytes), not truncationpix/nar.py— NAR: deterministic archive, no timestamps/uid, only executable bitpix/store_path.py— Fingerprint:<type>:sha256:<hex>:/nix/store:<name>→ compress → base32pix/derivation.py— ATerm.drvparser +hashDerivationModulo(breaks circular output-path deps)pix/daemon.py— Unix socket protocol: magic handshake, uint64-LE framing, stderr log draining
Also: pix/main.py (CLI), docs/ (MkDocs site)
Separate subdirectory that builds on pix's low-level primitives to provide a high-level package construction API, mapping Nix patterns to Python idioms.
pixpkgs/drv.py—drv()constructor (raw derivation primitive): takes readable args, runs the 6-step derivation pipeline (blank outputs → hashDerivationModulo → make_output_path → fill → serialize → .drv store path). Returns a frozenPackagedataclass without,__str__(string interpolation context), andoverride()(likepkg.overridein Nix).pixpkgs/fetchurl.py—fetchurl(): Python equivalent of<nix/fetchurl.nix>. Usesbuiltin:fetchurlbuilder for fixed-output downloads.pixpkgs/mk_derivation.py—mk_derivation(): Python equivalent ofstdenv.mkDerivation. Wrapsdrv()with standard env vars, source-stdenv.sh/default-builder.sh pattern.pixpkgs/package_set.py—PackageSetbase class withcall(): auto-injects dependencies viainspect.signature+getattr(Python equivalent of Nix'scallPackagepattern).pixpkgs/bootstrap/— Bootstrap chain stages (Stage0 → Stage1 → StageXgcc → ...). Hand-written package definitions inpixpkgs/pkgs/for key packages, hash-perfect against real nixpkgs.pixpkgs/bootstrap/closure.py—load_hello_closure(): auto-builds ALL 196 derivations innixpkgs#helloclosure from .drv files. Onepackage_from_drv()function handles all types (fetchurl, compiled, hooks, stdenvs). No per-package Python files needed for stage variants.pixpkgs/realize.py—realize(): recursively registers.drvfiles in the store viaadd_text_to_store, then builds viabuild_paths. Handles the full dep tree.
Python idioms mapping to Nix:
callPackage→inspect.signature+getattr(PackageSet.call)override→dataclasses.replace/ re-call with merged kwargs- String interpolation →
__str__returning output path - Lazy package attrs →
@cached_propertyon PackageSet
- Do NOT inspect .drv files when implementing bootstrap stages. Never use
nix derivation showto extract env vars — implement from source understanding. - Read nixpkgs .nix source files (
pkgs/stdenv/linux/default.nixand relevant package.nixfiles) to understand the logic. The nixpkgs source is accessible at the flake input path (find withnix eval .#inputs.nixpkgs.outPathor browsepkgs/in the store). - The reference nixpkgs is pinned in
flake.lock(nixos-24.11 branch). All expected hashes are relative to this revision. - Use pix library for hash computation — the repo has all machinery to compute derivation hashes from scratch (drv.py, store_path.py, hash.py). Verify by computing in Python, not by querying nix-store.
- Understand WHY each env var and dependency exists, don't mechanically copy.
- Bootstrap closure structure: 196 drvs in
nixpkgs#helloclosure, only 147 unique pnames. 31 packages are rebuilt across stages (bash 5x, gnu-config 6x). 58 are fetchurl sources, 13 are stdenvs, 16 are setup hooks. Don't write per-stage Python files for each rebuild —closure.pyauto-handles all 196 from .drv files. Hand-writepixpkgs/pkgs/*.pyonly for key packages where understanding the logic matters. placeholder("out")survives into .drv files unchanged. Replaced at build time by setup.sh, NOT during derivation construction. Seepix/store_path.py:placeholder().- Nix null-key pattern:
{ ${null} = "value"; }=={}. Used in make-derivation.nix for conditional env vars. - outputs env var order: "out" first, rest preserves Nix source order (NOT alphabetical). ATerm outputs section IS alphabetical.
- Type prefix has NO trailing colon when references list is empty (
textnottext:) - Daemon handshake order: read nix version string (>= 1.33) BEFORE trusted status (>= 1.35)
- NAR directory entries must be sorted lexicographically
- XOR-fold is NOT truncation — every input byte contributes to every output byte
hashDerivationModulohas two modes:mask_outputs=True(staticOutputHashes — blank own outputs to break circularity) vsmask_outputs=False(pathDerivationModulo — keep filled output paths for input derivation hashes)- Fixed-output derivation hash (Nix 2.28+):
sha256("fixed:out:<hashAlgo>:<hashValue>:<outputPath>")— includes the output path, not just a trailing colon - Nix build sandbox has no coreutils: use shell builtins (
echo,read,test) notcat,tr,cp