Derivation with empty outputs gets an uncharacteristic error message #1142

Open
opened 2026-02-27 21:55:14 +00:00 by raito · 2 comments
Owner

Derivation with empty outputs should receive a clear message that the output list is empty and should contain something.

Instead, we get:

❯ nix-build -E 'derivation { outputs = []; system = ":"; builder = ""; }'                                                      
error:
       … while evaluating an expression to select 'value' on it
         at «internal»:1:690:
       … while calling the 'head' builtin
         at «internal»:1:691:
       error: list index 0 is out of bounds

Problem is that we need to decide if the error is fatal or not, it is currently is. If we throw an abort from the builtin, it's also meh. If we throw a catchable error, well… it's now catchable!

All (strong) opinions welcome.

cc @pennae

Derivation with empty outputs should receive a clear message that the output list is empty and should contain something. Instead, we get: ``` ❯ nix-build -E 'derivation { outputs = []; system = ":"; builder = ""; }' error: … while evaluating an expression to select 'value' on it at «internal»:1:690: … while calling the 'head' builtin at «internal»:1:691: error: list index 0 is out of bounds ``` Problem is that we need to decide if the error is fatal or not, it is currently is. If we throw an abort from the builtin, it's also meh. If we throw a catchable error, well… it's now catchable! All (strong) opinions welcome. cc @pennae
raito added this to the 2.96 milestone 2026-03-03 02:30:14 +00:00
Member

I'd argue to keep it as a catchable error, but definitely with a better error message, e.g. "derivations must define at least one output", though this isn't a strong opinion

I'd argue to keep it as a catchable error, but definitely with a better error message, e.g. "derivations must define at least one output", though this isn't a strong opinion

three notes:

  • derivationStrict already has an error for this (derivation cannot have an empty set of outputs.) and it is an eval error that is not catchable
  • builtins.derivation is defined in libexpr/primops/derivation.nix (and calls derivationStrict, tho it actually doesn't get eval'd when outputs is empty) and does the head call that causes this error
  • ...but there's also libexpr/imported-drv-to-derivation.nix that (if i'm not mistaken) is used when doing IFD to a .drv file's attrs to a derivation, which means it'll probably never have to encounter an empty outputs array, but it could be a nice precaution to handle it anyway

for what it's worth, i do think it should be catchable, and it'd be a nice opportunity to change derivationStrict's error to also be catchable after discussing it with @raito and @piegames, i'm mostly trending towards non-catchable, though that isn't a very strong opinion. if anyone else has opinions, please don't hesitate to put them here, the debate still has to be had :)

the debate very quickly turned into a general "should most errors even exist/be catchable in nix," with the main points being:

  1. @raito: builtins.tryEval (the only thing really influenced by fatal vs. non-fatal) is mostly used in CI and non-interactive scenarios. Generally, this will be to allow getting some information about a package set and each derivation within it, including for multiple build/runtime contexts (architecture, libc, etc.). There are a lot of scenarios where we want to be able to partially evaluate those derivations, get some info about them, even if they aren't fully formed, and use it to create new data/derivations.
    • from this point of view, you could argue that making this error catchable would allow this operation to work on more scenarios
    • ...but in reality there is only a limited, cursed set of situations where you'd want the output list to depend on this sort of data, none of which are really natural
  2. @piegames, with @pennae and @raito agreeing: the above use case should be externalized into a separate tool or into the top-level nix command (e.g. a nix eval variant or option)
  3. @piegames: ideally, we'd get rid of tryEval by marking it as unsafe and impure, virtually excluding it from any code that is meant to produce derivations (which is generally pure)
  4. @raito, with @piegames agreeing: a lot of errors are basically unrecoverable and/or caused by outside factors (e.g. a FOD-mismatch error), and those are inherently impure, so they should not interact with nix code
  5. @blokyk, with @piegames agreeing: while derivation-related and externally-caused errors shouldn't be catchable, the lack of any handling for some normal runtime exceptions (e.g. builtins.elemAt throwing an uncatchable exception) can be annoying in nix, and there could be some design space to explore there

and a final note from @raito: at the end of the day, the general catchability debate is limited by the small number of scenarios we know of where catching this type of error is actually useful, especially outside of a CI/hydra context. ultimately, we need to gather experiences from a wider range of people and to get more debate going.

three notes: - `derivationStrict` [already has an error for this](https://git.lix.systems/lix-project/lix/src/commit/bcc9350bfe7e496b5aa5701f61b4e125ab40c7e4/lix/libexpr/primops.cc#L982-984) (`derivation cannot have an empty set of outputs.`) and it is an eval error that is not catchable - `builtins.derivation` is defined in [`libexpr/primops/derivation.nix`](https://git.lix.systems/lix-project/lix/src/branch/main/lix/libexpr/primops/derivation.nix) (and calls `derivationStrict`, tho it actually doesn't get eval'd when `outputs` is empty) and does the `head` call that causes this error - ...but there's also [`libexpr/imported-drv-to-derivation.nix`](https://git.lix.systems/lix-project/lix/src/branch/main/lix/libexpr/imported-drv-to-derivation.nix) that (if i'm not mistaken) is used when doing IFD to a `.drv` file's attrs to a `derivation`, which means it'll probably never have to encounter an empty `outputs` array, but it could be a nice precaution to handle it anyway ~~for what it's worth, i do think it should be catchable, and it'd be a nice opportunity to change `derivationStrict`'s error to also be catchable~~ after discussing it with @raito and @piegames, i'm mostly trending towards non-catchable, though that isn't a very strong opinion. if anyone else has opinions, please don't hesitate to put them here, the debate still has to be had :) the debate very quickly turned into a general "should most errors even exist/be catchable in nix," with the main points being: 1. @raito: `builtins.tryEval` (the only thing really influenced by fatal vs. non-fatal) is mostly used in CI and non-interactive scenarios. Generally, this will be to allow getting *some* information about a package set and each derivation within it, including for multiple build/runtime contexts (architecture, libc, etc.). There are a lot of scenarios where we want to be able to partially evaluate those derivations, get some info about them, even if they aren't fully formed, and use it to create new data/derivations. - from this point of view, you could argue that making this error catchable would allow this operation to work on more scenarios - ...but in reality there is only a limited, _cursed_ set of situations where you'd want the output list to depend on this sort of data, none of which are really natural 2. @piegames, with @pennae and @raito agreeing: the above use case should be externalized into a separate tool or into the top-level nix command (e.g. a `nix eval` variant or option) 3. @piegames: ideally, we'd get rid of `tryEval` by marking it as unsafe and impure, virtually excluding it from any code that is meant to produce derivations (which is generally pure) 4. @raito, with @piegames agreeing: a lot of errors are basically unrecoverable and/or caused by outside factors (e.g. a FOD-mismatch error), and those are inherently impure, so they should not interact with nix code 5. @blokyk, with @piegames agreeing: while derivation-related and externally-caused errors shouldn't be catchable, the lack of any handling for some normal runtime exceptions (e.g. `builtins.elemAt` throwing an uncatchable exception) can be annoying in nix, and there could be some design space to explore there and a final note from @raito: at the end of the day, the general catchability debate is limited by the small number of scenarios we know of where catching this type of error is actually useful, especially outside of a CI/hydra context. ultimately, we need to gather experiences from a wider range of people and to get more debate going.
Sign in to join this conversation.
No milestone
No project
No assignees
3 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lix-project/lix#1142
No description provided.