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.

Install

curl -fsSL codexskills.dev/install.sh \  | sh -s -- nix-flakes

Installs to ~/.agents/skills/nix-flakes/ for every project on this machine.

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.

~/.agents/skills/nix-flakes/SKILL.md

--- name: nix-flakes description: Writes and maintains flake.nix files for reproducible development shells and builds - pinned nixpkgs inputs and flake.lock updates, devShells with… license: MIT ---

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

  1. nix --version and whether flakes are enabled: nix config show | grep experimental-features should include nix-command flakes (Determinate Nix enables them by default). If not, add experimental-features = nix-command flakes to ~/.config/nix/nix.conf.
  2. Read AGENTS.md, .tool-versions, .nvmrc, go.mod, rust-toolchain.toml and CI to learn required tool versions.
  3. Look for an existing flake.nix, shell.nix, default.nix and .envrc.
  4. 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 develop or nix build for 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-style is being renamed to nixfmt in 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).

LanguageBuilderHash to set
NodebuildNpmPackagenpmDepsHash
GobuildGoModulevendorHash (null if vendored)
RustrustPlatform.buildRustPackagecargoHash
Pythonpython3Packages.buildPythonApplication with pyproject = truenone 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, run nix build, copy the got: sha256-... value into the file, rebuild. Every lockfile change needs a new hash; document this in AGENTS.md.
  • Builds run in a sandbox without network. Tests that call the internet must be skipped in checkPhase (or doCheck = false with a comment).
  • Expose checks (for example checks = forAllSystems (pkgs: { build = self.packages.${pkgs.stdenv.hostPlatform.system}.default; });) so nix flake check builds 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> in AGENTS.md so Codex runs tests inside the pinned toolchain.
  • If the Codex sandbox has no network and the store does not already hold the closure, nix develop fails; ask the user to run it once (or approve network) to populate /nix/store.

Gotchas

  • nix develop gives a build environment for the shell; nix shell nixpkgs#jq just adds binaries to PATH. Use develop for project shells.
  • shellHook runs on every entry; keep it fast and side-effect free (no installs).
  • legacyPackages is the normal way to access nixpkgs packages despite the name.
  • Mixing nixpkgs Python packages with pip install into the same interpreter breaks isolation. Pick one: nixpkgs provides uv/Python, the project’s lockfile provides libraries.
  • macOS: some packages are Linux-only; guard with pkgs.lib.optionals pkgs.stdenv.isLinux [ ... ].

Verification

  • git add done; nix flake check passes on Linux and macOS (CI matrix or both hosts)
  • nix develop -c <tool> --version matches CI and .tool-versions
  • nix develop -c <test command> passes from a clean clone
  • flake.lock committed; second machine gets identical store paths (nix path-info .#default equal)

What the zip contains

SKILL.md plus the interface file below. Nothing runs on install.

agents/openai.yamlgenerated from this page

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.

esc
Just launched

Codex Skills Pro is live

79 premium skills for Codex, each with scripts, references and an evidence report that proves the result.

Bought one by one$882.21
All 79 skills$49
94% off

That's $0.62 a skill, paid once.

Get all 79 for $49Or pick single skills from $4.99 →

Secure checkout by Creem