This document describes the custom composite GitHub Action defined in .github/actions/setup-nix/action.yaml that provisions the Nix-based development environment in CI/CD workflows. The action ensures that CI environments are identical to local development environments by installing Nix, provisioning tools from nixpkgs, and initializing project dependencies.
For information about the local development environment that this action replicates, see Development Environment Setup. For details on how this action is used in specific workflows, see CI Workflow and Release Workflow.
Sources: .github/actions/setup-nix/action.yaml1-51
The setup-nix composite action is a reusable workflow component that establishes environment parity between local development (managed by flake.nix and direnv) and CI runners. It performs four primary functions:
cachix/install-nix-actionuv, ty, just)uv sync and Node.js dependencies for the MCP mock serverThe action is designed to be composable—workflows can skip dependency installation if only Nix tools are needed, or specify alternative tool sets.
Sources: .github/actions/setup-nix/action.yaml1-12
The action accepts two optional inputs that control its behavior:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
tools | string | No | "uv ty just" | Space-separated list of nixpkgs packages to install. Each tool is prefixed with nixpkgs# during installation. |
skip-uv-sync | string | No | "false" | When "true", skips git submodule initialization, Python dependency installation, and MCP server dependency installation. Useful for workflows that only need Nix tools. |
Sources: .github/actions/setup-nix/action.yaml3-11
Execution Flow Diagram: This diagram shows the conditional logic in the action. The skip-uv-sync parameter acts as a gate, bypassing all dependency installation steps when enabled. Submodule and MCP server checks ensure idempotency—steps only run when necessary.
Sources: .github/actions/setup-nix/action.yaml13-51
This step uses the official Cachix action to install Nix on the GitHub Actions runner. The pinned commit hash (0b0e072294b088b73964f1d72dfdac0951439dbd) ensures reproducibility—the exact version v31.8.4 is used regardless of future updates to the action.
The github_access_token is passed to authenticate against GitHub's API when fetching Nix inputs from flake.lock, avoiding rate limiting issues.
Sources: .github/actions/setup-nix/action.yaml15-18
This step constructs a nix profile install command by iterating over the space-separated tools input. Each tool is prefixed with nixpkgs# to reference packages from the nixpkgs flake input.
The --inputs-from . flag instructs Nix to use the flake inputs defined in flake.lock, ensuring the installed tools come from the same nixpkgs revision used locally. This is critical for environment parity—a developer running nix develop locally will have the same uv version as CI.
Example: For tools="uv ty just", the command becomes:
Sources: .github/actions/setup-nix/action.yaml20-28 flake.nix5
This step conditionally initializes git submodules. The guard conditions ensure idempotency:
.gitmodules file must exist (project has submodules)vendor/stackone-ai-node/package.json must be absent (submodules not yet checked out)This matches the behavior in the local flake.nix shell hook at flake.nix131-135 ensuring that both local and CI environments handle the MCP mock server submodule identically.
Sources: .github/actions/setup-nix/action.yaml30-37 flake.nix131-135
This step installs Python dependencies using uv sync with two critical flags:
--all-extras: Installs all optional dependency groups defined in pyproject.toml (e.g., dev, test, integrations)--locked: Uses the exact versions specified in uv.lock, failing if the lock file is out of sync with pyproject.tomlThis mirrors the local environment setup at flake.nix138-141 which also runs uv sync --all-extras --locked in the shellHook.
Sources: .github/actions/setup-nix/action.yaml39-42 flake.nix138-141
This step installs Node.js dependencies for the MCP mock server located in the vendor/stackone-ai-node git submodule. The --frozen-lockfile flag ensures pnpm uses the exact versions in pnpm-lock.yaml without updating it, paralleling the behavior of --locked in uv sync.
The conditional check verifies that the submodule has been initialized before attempting installation, handling cases where submodule initialization might have been skipped or failed.
Sources: .github/actions/setup-nix/action.yaml44-50
The following table compares the local development environment (established by flake.nix and direnv) with the CI environment (established by setup-nix action):
| Component | Local Environment | CI Environment | Parity Mechanism |
|---|---|---|---|
| Nix Installation | Pre-installed or via Nix installer | cachix/install-nix-action@v31.8.4 | Both use Nix from flake.lock inputs |
| Tool Versions | mkShellNoCC with buildInputs flake.nix112-126 | nix profile install --inputs-from . action.yaml28 | --inputs-from . references same flake.lock |
| Submodule Init | shellHook conditional check flake.nix131-135 | setup-nix conditional check action.yaml34-36 | Identical conditional logic |
| Python Deps | uv sync --all-extras --locked flake.nix140 | uv sync --all-extras --locked action.yaml42 | Identical command, uses same uv.lock |
| MCP Server Deps | Not in shellHook (manual) | pnpm install --frozen-lockfile action.yaml49 | CI includes explicit step |
| Pre-commit Hooks | Installed via shellHook flake.nix144 | Not installed in CI | CI runs checks directly in workflows |
| Agent Skills | mkShellHook installs skills flake.nix146-149 | Not installed in CI | CI doesn't need agent-skills runtime |
Pre-commit Hooks: Local environment installs git hooks automatically via pre-commit-check.shellHook, while CI workflows invoke checkers (ruff, ty, gitleaks) directly as workflow steps.
Agent Skills: The agent-skills-nix system (just-commands, release-please) is installed locally via mkShellHook but not in CI, as these are development-time utilities not needed for testing or building.
MCP Server: CI explicitly installs Node.js dependencies for the MCP mock server, while local developers must run this manually after nix develop.
Sources: flake.nix112-150 .github/actions/setup-nix/action.yaml13-51
Action Lifecycle Diagram: This diagram shows the data flow from lock files to the provisioned CI environment. Each step depends on a specific lock file for reproducibility: flake.lock controls Nix tool versions, uv.lock controls Python packages, and pnpm-lock.yaml controls the MCP server's Node.js packages.
Sources: .github/actions/setup-nix/action.yaml13-51 flake.nix1-153
The setup-nix action is invoked in multiple workflows with different configurations:
The CI workflow uses the action with default parameters, provisioning a complete test environment including Python dependencies for running pytest and type checking with ty.
Sources: .github/actions/setup-nix/action.yaml7-11
The release workflow overrides tools to install only uv (just and ty are not needed for building packages), but keeps skip-uv-sync as "false" to install Python dependencies required for uv build.
Sources: .github/actions/setup-nix/action.yaml4-11
The flake update workflow uses skip-uv-sync: "true" because it only needs just to run flake checks—it doesn't execute any Python code, so dependency installation is unnecessary. This reduces workflow execution time.
Sources: .github/actions/setup-nix/action.yaml8-11
The setup-nix action provides three levels of reproducibility:
The action uses a pinned commit hash for cachix/install-nix-action:
This ensures the Nix installer itself is reproducible—CI will always use the exact same action version, even if newer releases are published.
Sources: .github/actions/setup-nix/action.yaml16
The --inputs-from . flag in nix profile install instructs Nix to use the flake inputs (including nixpkgs) specified in flake.lock. This means:
uv version is determined by the nixpkgs revision at flake.lock (e.g., github:NixOS/nixpkgs/nixpkgs-unstable)flake.lock (via nix flake update or Dependabot) changes tool versions for both local and CISources: .github/actions/setup-nix/action.yaml28 flake.nix5
Both uv sync --locked and pnpm install --frozen-lockfile enforce lock file integrity:
pyproject.toml changes without uv.lock being updated, the action failspackage.json changes without pnpm-lock.yaml being updated, the action failsThis prevents "works on my machine" issues where a developer has different dependencies than CI due to stale lock files.
Sources: .github/actions/setup-nix/action.yaml42 .github/actions/setup-nix/action.yaml49
The following table contrasts the setup-nix approach with typical GitHub Actions environment provisioning:
| Aspect | Standard Actions Setup | setup-nix Action |
|---|---|---|
| Python Version | actions/setup-python@v5 with version matrix | Managed by nixpkgs revision in flake.lock |
| Package Manager | pip, poetry, or pdm installed separately | uv installed via Nix from pinned nixpkgs |
| Lock File Format | requirements.txt, poetry.lock, or pdm.lock | uv.lock (faster resolver, cryptographic hashes) |
| Additional Tools | Installed via apt-get, brew, or separate actions | Installed via nix profile install from same nixpkgs |
| Cache Strategy | actions/cache with manual key management | Nix store with built-in content-addressed caching |
| Version Drift Risk | High (different apt/brew versions on different runners) | Low (Nix uses exact versions from flake.lock) |
| Local/CI Parity | Separate setup (developer installs tools manually) | Identical (nix develop locally, setup-nix in CI) |
Sources: .github/actions/setup-nix/action.yaml1-51 flake.nix1-153
Cause: The runner doesn't have access to the repository's flake.lock file, typically because the action is invoked before checking out the repository.
Solution: Ensure actions/checkout@v4 runs before setup-nix:
Cause: pyproject.toml dependencies were modified without regenerating uv.lock.
Solution: Run uv lock locally and commit the updated lock file:
Cause: Tests are running outside the uv-managed virtualenv, so installed packages aren't found.
Solution: Prefix test commands with uv run:
The uv run prefix activates the virtualenv created by uv sync in the action.
Sources: .github/actions/setup-nix/action.yaml39-42
The following improvements are under consideration for the setup-nix action:
Nix Binary Cache: Configure a custom binary cache (e.g., Cachix) to speed up tool installation by caching pre-built Nix packages.
Parameterized Python Extras: Allow workflows to specify which pyproject.toml extras to install instead of always using --all-extras, reducing dependency installation time for workflows that don't need test or integration extras.
Conditional MCP Server Install: Add an input parameter to control whether MCP server dependencies are installed, allowing workflows that don't use the mock server to skip this step.
Pre-commit Hook Support: Add an optional input to install pre-commit hooks in CI, enabling workflows to run the same linting/formatting checks that developers run locally via git hooks.
Refresh this wiki