2 13 Specialargs
Nimmo edited this page 2026-05-30 22:40:08 +01:00

Chapter 13: specialArgs and the Inputs Pipeline

This chapter explains how flake inputs travel from flake.nix into every module in your configuration. This is one of the concepts people find most confusing, so we will go through it step by step.

The Problem

Your modules need access to external flake inputs. For example, home/nimmo.nix needs to reference packages from claude-code-nix and the bash-it source. But how does a module deep in the file tree know about inputs declared in flake.nix?

The Solution: specialArgs

In flake.nix, the makeNixosSystem helper passes inputs through specialArgs for every host:

makeNixosSystem = { pkgs, hmFlake, configName, isServer }:
  pkgs.lib.nixosSystem {
    specialArgs = { inherit inputs; };
    modules = [ ./hosts/${configName} ... ];
  };

Let us unpack this line by line.

What specialArgs does

specialArgs is an attrset of extra arguments that NixOS passes to every module function. Normally, modules receive config, pkgs, lib, etc. With specialArgs = { inherit inputs; };, every module also receives inputs.

What inherit inputs means

Recall from Chapter 9:

{ inherit inputs; }
# is equivalent to:
{ inputs = inputs; }

The inputs variable comes from the outputs function parameter:

outputs = { self, nixpkgs, nixpkgs-unstable-small, nixpkgs-stable, home-manager,
            claude-code-nix, codex-cli-nix, nixos-hardware, disko, bash-it, nur, sops-nix,
            ... }@inputs:

The @inputs binds the entire argument attrset to the name inputs. So inputs is an attrset containing:

{
  self = <this flake>;
  nixpkgs = <the nixpkgs flake>;
  nixpkgs-unstable-small = <the unstable-small nixpkgs flake>;
  nixpkgs-stable = <the stable nixpkgs flake>;
  home-manager = <the home-manager flake>;
  claude-code-nix = <the claude-code flake>;
  codex-cli-nix = <the codex-cli flake>;
  nixos-hardware = <the hardware flake>;
  disko = <the disko flake>;
  bash-it = <the bash-it source tree>;
  nur = <the NUR flake>;
  sops-nix = <the sops-nix flake>;
  plasma-manager = <the plasma-manager flake>;
  framework-system = <the Framework hardware tool flake>;
}

This entire attrset gets passed to every module (via specialArgs for NixOS modules, and via extraSpecialArgs for home-manager modules).

How Modules Receive It

Any module that needs inputs simply adds it to its function arguments:

{ config, pkgs, inputs, ... }:

Modules that do not need inputs do not list it, and the ... silently ignores it:

{ config, pkgs, ... }:

Which of your modules use inputs?

Let us check the key files:

File Uses inputs? Why?
hosts/common/default.nix Yes Imports inputs.disko.nixosModules.disko
hosts/common/nix-settings.nix No Only uses config and pkgs
hosts/common/locale.nix No Only uses config and pkgs
hosts/electra/default.nix Yes Imports inputs.nixos-hardware.nixosModules.*
hosts/electra/hardware.nix No Only uses config, pkgs, lib
hosts/electra/hardware-dgpu.nix No Only uses config, pkgs, lib
hosts/lena/default.nix Yes Imports inputs.nixos-hardware.nixosModules.*
home/nimmo.nix Yes References inputs.claude-code-nix, inputs.bash-it, etc.
users/nimmo.nix No Just account definition (groups only)
Most modules in modules/ No Most do not need external flake packages

Note that home/nimmo.nix receives inputs through a different mechanism than NixOS modules. While NixOS modules get inputs via specialArgs, home-manager modules get it via extraSpecialArgs (set in flake.nix):

home-manager.extraSpecialArgs = {
  inherit inputs;
  isServer = isServer;
  flakeRepo = config.nixosConfig.flakeRepo;
  primaryUser = config.nixosConfig.primaryUser;
  userEmail = config.nixosConfig.userEmail;
};

The additional parameters (isServer, flakeRepo, primaryUser, userEmail) allow home/nimmo.nix to adjust behaviour per host. isServer is the most visible: it gates desktop-only packages.

The Flow, End to End

Here is the complete pipeline for how claude-code-nix gets from a GitHub repository to being installed in nimmo's PATH:

1. flake.nix declares the input:
   inputs.claude-code-nix.url = "github:sadjow/claude-code-nix";

2. flake.lock pins it to a specific revision:
   "rev": "84d6591a3547c45ee177131aa09a64caefcf5c14"

3. The outputs function receives it:
   outputs = { ..., claude-code-nix, ... }@inputs:

4. makeNixosSystem passes inputs and isServer to home-manager modules:
   home-manager.extraSpecialArgs = { inherit inputs; isServer = isServer; ... };

5. home/nimmo.nix (or a profile module) receives inputs and uses it:
   { config, pkgs, inputs, ... }:
   {
     home.packages = [
       inputs.claude-code-nix.packages.${pkgs.stdenv.hostPlatform.system}.claude-code
     ];
   }

6. Home-manager evaluates this, downloads/builds the package, and puts it in nimmo's PATH.

Every external package follows this same pipeline. The steps are:

  1. Declare the input in flake.nix
  2. Let flake.lock pin it
  3. It arrives in the outputs function parameter
  4. specialArgs (for NixOS modules) or extraSpecialArgs (for home-manager modules) passes it through
  5. A module references it via inputs.<name>

Using specialArgs for Other Things

While your configuration only passes inputs, you could pass anything through specialArgs:

electra = nixpkgs.lib.nixosSystem {
  specialArgs = {
    inherit inputs;
    myCustomValue = "hello";
    featureFlags = { experimentalUI = true; };
  };
  modules = [ ./hosts/electra ];
};

Then in any module:

{ config, pkgs, myCustomValue, featureFlags, ... }:

{
  # myCustomValue is "hello"
  # featureFlags.experimentalUI is true
}

This can be useful for passing host-specific configuration values without hardcoding them in modules. However, for most cases, the NixOS options system (Chapter 12) is a better approach.

Common Mistakes

Forgetting inputs in the function arguments

If a module uses inputs but does not list it:

{ config, pkgs, ... }:     # Missing inputs!

{
  users.users.nimmo.packages = [
    inputs.claude-code-nix.packages.${pkgs.stdenv.hostPlatform.system}.claude-code
    # Error: undefined variable 'inputs'
  ];
}

The fix: add inputs to the argument list:

{ config, pkgs, inputs, ... }:

Using inputs in a module that is not passed specialArgs

If you use nixpkgs.lib.nixosSystem without specialArgs, inputs is not available:

electra = nixpkgs.lib.nixosSystem {
  # No specialArgs!
  modules = [ ./hosts/electra ];
};

This would break any module that tries to use inputs. Always include specialArgs = { inherit inputs; };.

Confusing pkgs with inputs.nixpkgs

pkgs is the evaluated package set -- you use it to get packages: pkgs.firefox, pkgs.git.

inputs.nixpkgs is the nixpkgs flake itself -- you use it for things like nixpkgs.lib.nixosSystem.

They come from the same source, but pkgs has been "instantiated" with your system's platform and configuration (like allowUnfree), while inputs.nixpkgs is the raw flake.