Nix Flakes skill for Codex
One pinned toolchain from laptop to CI. Codex writes flakes that pin nixpkgs in flake.lock, expose a multi-system dev shell with versioned tool attributes, and build packages with the right dependency hash workflow. It remembers that flakes only see Git-tracked files, wires direnv for automatic shells, runs nix flake check in CI with a binary cache, and records nix develop commands in AGENTS.md so Codex tests inside the same toolchain.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- nix-flakes Installs to ~/.agents/skills/nix-flakes/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install nix-flakes The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project nix-flakes Installs to .agents/skills/nix-flakes/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/nix-flakes.zipunzip -o nix-flakes.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) nix-flakes PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $nix-flakes, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Writes and maintains flake.nix files for reproducible development shells and builds - pinned nixpkgs inputs and flake.lock updates, devShells with mkShell for multi-language toolchains, packages built with buildNpmPackage, buildGoModule or rustPlatform with dependency hashes, per-system outputs, nix flake check, direnv integration with nix-direnv, formatter outputs, and CI with a binary cache. Use when a repo needs a reproducible toolchain, adding or fixing a flake.nix, a Nix build fails on hashes or missing files, or onboarding should install every tool with one command.
Use it when
- A project wants exact, shared tool versions without containers
- Adding or fixing flake.nix, flake.lock or a .envrc with use flake
- A Nix build fails on hash mismatches or missing files
- CI should build with the same toolchain developers use
Use something else when
- The team standardizes on containers and VS Code: use devcontainers
- You only need a task runner for project commands: use makefiles-taskfiles
How to check the result
nix flake check passes on every listed system, nix develop -c reports the expected tool versions, and the test command passes inside the shell from a clean clone.
The mistake it prevents
Creating flake.nix or new source files without git add, so the flake cannot see them and builds fail with a missing path.
A good flake pins every input in flake.lock, gives contributors one command
(nix develop or direnv allow) that produces the exact toolchain CI uses, builds
packages without network access during the build, and passes nix flake check on every
supported system. It stays small: nixpkgs for tools, the language’s own lockfile for
libraries.
Inspect first
nix --versionand whether flakes are enabled:nix config show | grep experimental-featuresshould includenix-command flakes(Determinate Nix enables them by default). If not, addexperimental-features = nix-command flakesto~/.config/nix/nix.conf.- Read
AGENTS.md,.tool-versions,.nvmrc,go.mod,rust-toolchain.tomland CI to learn required tool versions. - Look for an existing
flake.nix,shell.nix,default.nixand.envrc. - Nix evaluation and builds fetch from the network and the binary cache; in a sandbox
without network they fail. Ask for network approval before running
nix developornix buildfor the first time.
Dev shell baseline
{
description = "acme api dev environment";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-25.11"; # a stable release branch
};
outputs = { self, nixpkgs }:
let
systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f nixpkgs.legacyPackages.${system});
in {
devShells = forAllSystems (pkgs: {
default = pkgs.mkShell {
packages = with pkgs; [
nodejs_22
pnpm
python313
uv
postgresql_17
just
];
shellHook = ''
export PGDATA="$PWD/.dev/pg"
echo "node $(node -v), python $(python3 --version | cut -d' ' -f2)"
'';
};
});
formatter = forAllSystems (pkgs: pkgs.nixfmt-rfc-style);
};
}
nix develop # enter the shell
nix develop -c npm test # run one command in it (good for CI and Codex)
nix flake show # list outputs
nix flake check # evaluate all outputs and run checks
nix fmt # format with the declared formatter
- Prefer versioned attribute names (
nodejs_22,python313,postgresql_17) so a nixpkgs bump cannot silently change major versions. - Package names change across releases; confirm with
nix search nixpkgs#<name>(network) rather than guessing. nixfmt-rfc-styleis being renamed tonixfmtin newer nixpkgs; use whichever attribute the pinned nixpkgs provides.
Git and flake visibility
In a Git repo, flakes only see files tracked by Git. A new flake.nix or any new source
file that is untracked produces “path does not exist” or a stale build. Run git add
(staging is enough; no commit needed) before nix build or nix develop.
direnv
# .envrc
use flake
Install nix-direnv for cached shells, then direnv allow. The shell loads on cd, and
editors with direnv support pick up the same tools. Add .direnv/ to .gitignore.
Updating inputs
nix flake update # update every input, rewrite flake.lock
nix flake update nixpkgs # update one input (Nix 2.19+)
nix flake lock --override-input nixpkgs github:NixOS/nixpkgs/<rev> # pin a specific rev
Treat flake.lock changes like dependency upgrades: their own PR, CI green, and a note of
which tool versions moved (nix develop -c node -v before and after).
Packages
Build the project itself only when it adds value (CI parity, deployment, distribution).
| Language | Builder | Hash to set |
|---|---|---|
| Node | buildNpmPackage | npmDepsHash |
| Go | buildGoModule | vendorHash (null if vendored) |
| Rust | rustPlatform.buildRustPackage | cargoHash |
| Python | python3Packages.buildPythonApplication with pyproject = true | none for nixpkgs deps |
packages = forAllSystems (pkgs: {
default = pkgs.buildGoModule {
pname = "api";
version = "0.4.0";
src = ./.;
vendorHash = pkgs.lib.fakeHash; # replace with the hash from the first build error
subPackages = [ "cmd/api" ];
};
});
- Hash workflow: set
lib.fakeHash, runnix build, copy thegot: sha256-...value into the file, rebuild. Every lockfile change needs a new hash; document this inAGENTS.md. - Builds run in a sandbox without network. Tests that call the internet must be skipped in
checkPhase(ordoCheck = falsewith a comment). - Expose
checks(for examplechecks = forAllSystems (pkgs: { build = self.packages.${pkgs.stdenv.hostPlatform.system}.default; });) sonix flake checkbuilds them in CI.
CI
- uses: cachix/install-nix-action@v31
- uses: cachix/cachix-action@v16
with:
name: acme
authToken: ${{ secrets.CACHIX_AUTH_TOKEN }}
- run: nix flake check
- run: nix develop -c npm test
Pin actions by SHA in production repos. A binary cache (Cachix, FlakeHub Cache, or an S3 or Attic cache) turns repeat builds from minutes into seconds. Pushing to a shared cache is outward-facing: confirm the cache name and who can read it.
Using it with Codex
- Record
nix develop -c <test command>inAGENTS.mdso Codex runs tests inside the pinned toolchain. - If the Codex sandbox has no network and the store does not already hold the closure,
nix developfails; ask the user to run it once (or approve network) to populate/nix/store.
Gotchas
nix developgives a build environment for the shell;nix shell nixpkgs#jqjust adds binaries to PATH. Usedevelopfor project shells.shellHookruns on every entry; keep it fast and side-effect free (no installs).legacyPackagesis the normal way to access nixpkgs packages despite the name.- Mixing nixpkgs Python packages with
pip installinto the same interpreter breaks isolation. Pick one: nixpkgs providesuv/Python, the project’s lockfile provides libraries. - macOS: some packages are Linux-only; guard with
pkgs.lib.optionals pkgs.stdenv.isLinux [ ... ].
Verification
-
git adddone;nix flake checkpasses on Linux and macOS (CI matrix or both hosts) -
nix develop -c <tool> --versionmatches CI and.tool-versions -
nix develop -c <test command>passes from a clean clone -
flake.lockcommitted; second machine gets identical store paths (nix path-info .#defaultequal)
What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "Nix Flakes"
short_description: "One pinned toolchain from laptop to CI"
default_prompt: "$nix-flakes Write a flake.nix dev shell with Go 1.24, Node 22 and Postgres 17" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.