An action for GitHub Actions and Forgejo Actions that compares Nix derivations between base and PR branches using dix.
Warning
Alpha Version - This action is under active development. APIs and behavior may change without notice. We recommend pinning to a specific release version or commit SHA.
- Compare Nix derivations and post diff results as PR comments
- Support for multiple derivations
- Three operation modes for different use cases
- Compare derivation (.drv) or built output paths
- Nix with flakes enabled (requires a Nix installer action, e.g., cachix/install-nix-action)
Note
This action now only supports Nix flakes. Traditional Nix expressions (nix-build) are not supported.
dix is built from the nixpkgs input of the base branch's flake, so its version follows your nixpkgs. dix 1.x, 2.x, and 3.x are supported. With dix 2.0 or later, the action reads dix's JSON report and renders package tables; dix 1.x has no JSON output, so its human-readable output is shown as is.
name: Nix Diff
on:
pull_request:
jobs:
nix-diff:
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: write
steps:
- uses: actions/checkout@main
- uses: cachix/install-nix-action@main
- uses: natsukium/nix-diff-action@main
with:
attributes: |
- displayName: my-nixos
attribute: nixosConfigurations.my-nixos.config.system.build.toplevel
- displayName: my-macos
attribute: darwinConfigurations.my-macos.systemForgejo Runner exposes GitHub-compatible event variables, and nix-diff-action
uses the Forgejo server and API URLs for pull request comments and compare
links. Workflows live in .forgejo/workflows. The runner must provide Nix:
the example targets a host runner labelled x86_64-linux that has it
installed; on a container runner, add a step that installs Nix first.
name: Nix Diff
on:
pull_request:
jobs:
nix-diff:
runs-on: x86_64-linux
steps:
- uses: https://data.forgejo.org/actions/checkout@v6
- uses: https://github.com/natsukium/nix-diff-action@main
with:
attributes: |
- displayName: my-nixos
attribute: nixosConfigurations.my-nixos.config.system.build.toplevelOnly full mode works on Forgejo. diff-only and comment-only hand results
between jobs as artifacts, and artifact upload currently fails on Forgejo:
@actions/artifact 6.2 and later send a mime_type field that Forgejo's
CreateArtifactRequest protobuf does not define, and Forgejo rejects the whole
request rather than ignoring the unknown field. Full mode tolerates this — it
logs a warning, skips the artifact, and still posts the comment. Because no
artifact holds the full output, the comment is the only place the diff appears;
Forgejo imposes no comment length limit, so max-comment-length defaults to
1,000,000 characters there instead of GitHub's 60,000. Comments that a newer
run supersedes stay visible rather than being collapsed, because Forgejo has no
GraphQL API to minimize them.
For most users. Runs diffs sequentially and posts a single comment.
- uses: natsukium/nix-diff-action@main
with:
attributes: |
- displayName: host1
attribute: nixosConfigurations.host1.config.system.build.toplevel
- displayName: host2
attribute: nixosConfigurations.host2.config.system.build.toplevelFor parallel execution with matrix strategy. Runs diff and uploads results as artifacts. Typically used in combination with the following comment-only mode.
Aggregates all diff results from artifacts and posts a single PR comment. Used together with diff-only mode.
Here's an example of combining both modes:
jobs:
diff:
runs-on: ubuntu-latest
permissions:
contents: read
strategy:
matrix:
host: [host1, host2, host3]
steps:
- uses: actions/checkout@main
- uses: cachix/install-nix-action@main
- uses: natsukium/nix-diff-action@main
with:
mode: diff-only
attributes: |
- displayName: ${{ matrix.host }}
attribute: nixosConfigurations.${{ matrix.host }}.config.system.build.toplevel
comment:
needs: diff
runs-on: ubuntu-latest
permissions:
actions: read
pull-requests: write
steps:
- uses: natsukium/nix-diff-action@main
with:
mode: comment-only| Input | Required | Default | Description |
|---|---|---|---|
mode |
No | full |
Operation mode: full, diff-only, or comment-only |
attributes |
No* | - | Nix attributes in YAML format. Each item should have displayName and attribute keys. |
build |
No | false |
Build derivations before comparing (see details below) |
directory |
No | . |
Directory containing flake.nix |
github-token |
No | ${{ github.token }} |
GitHub or Forgejo token for posting comments |
skip-no-change |
No | true |
Skip posting comment when there are no differences |
comment-strategy |
No | create |
create to always create new comment, update to update existing comment |
max-comment-length |
No | 60000 (GitHub), 1000000 (Forgejo) | Maximum comment length in characters; longer diffs are truncated |
* attributes is required for full and diff-only modes. Not required for comment-only mode (which reads from artifacts).
By default, nix-diff-action compares derivation files (.drv) without building. This is fast and suitable for most cases.
Set build: true to build derivations and compare output paths instead. This enables closure size comparison but significantly increases workflow execution time. The workflow must run on a platform matching the derivation (e.g., x86_64-linux derivations require Linux runners, aarch64-darwin derivations require macOS ARM runners).
| Output | Description |
|---|---|
diff |
JSON array of diff results, one per attribute. |
Each item of diff has these keys:
| Key | Description |
|---|---|
displayName |
Display name of the attribute |
attributePath |
Nix attribute path that was compared |
basePath |
Store path on the base branch |
prPath |
Store path on the PR branch |
hasChanges |
Whether the two store paths differ |
format |
json with dix 2.0 or later, text with dix 1.x |
report |
The report from dix --output json, present when format is json. Its schema is defined by dix. |
text |
The human-readable dix output, present when format is text |
The action posts a comment like this to your PR:
Attribute:
nixosConfigurations.host1.config.system.build.toplevel
Base PR Diff Closure size 29.7 GiB29.7 GiB-19.3 MiBStore paths 4210 4206 +38 / −42 Changed (6)
Package Version Size 7zz24.09 → 25.00+12.1 KiB grim1.4.1 → 1.5.0+1.4 KiB hyprutils0.7.1 → 0.8.1+8.3 KiB nixos-system-temperance25.11.19800101.2a21304 → 25.11.19800101.be9e214thunderbird139.0.2 → 140.0+2.0 MiB thunderbird-unwrapped139.0.2 → 140.0+2.1 MiB Added (1)
Package Version Size hostname-hostname-debian3.25+98.6 KiB Removed (5)
Package Version Size hostname-net-tools2.10-88.0 KiB net-tools2.10-1.1 MiB onefetch2.24.0-14.6 MiB swww0.10.3-3.2 MiB unit-swww.service<none>-1.0 KiB 12 packages with only a minor size change hidden.
For a NixOS or nix-darwin system compared with build: true, packages listed explicitly in environment.systemPackages of either side are shown first in each group and in bold, and a Changed package notes whether it became explicit or is now a dependency only. dix cannot find them without a build, so other diffs are unaffected.
Like dix's own output, packages whose version did not change and whose size changed by less than 1 MiB are hidden from the comment. The full report is available in the diff output and the workflow artifacts.
Each package group is collapsed by default. Version lists longer than five entries per side are shortened with "+N more". If the comment would exceed GitHub's size limit, the space is shared between the groups and the remaining package rows (and, with many attributes, whole attributes) are omitted with a note pointing to the full results in the workflow artifacts.
Required permissions vary by mode:
| Mode | contents: read |
pull-requests: write |
actions: read |
|---|---|---|---|
full |
✓ (git operations) | ✓ (post comments) | - |
diff-only |
✓ (git operations) | - | - |
comment-only |
- | ✓ (post comments) | ✓ (download artifacts) |
On Forgejo, workflow permissions entries are ignored; the automatic token is
scoped to the repository the workflow runs in.
When using this action with pull requests from forks, the default GITHUB_TOKEN has limited permissions and cannot post comments to the PR. To enable commenting on fork PRs, use pull_request_target trigger.
Caution
When using pull_request_target, the workflow runs in the context of the base branch with write permissions. Always use actions/checkout with ref: ${{ github.event.pull_request.head.sha }} to checkout the PR's code, and be cautious about running untrusted code from forks.
GPL-3.0