builtins.readFile permission denied in post-build-hook nix copy but not in build

I’m attempting to set up a binary cache on s3 in our CI environment.

The /etc/nix.conf file is set up in CI as:

substituters = https://cache.nixos.org/ s3://our-cache?region=us-east-2
trusted-substituters = https://cache.nixos.org/ s3://our-cache?region=us-east-2
trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= our-cache:key=
secret-key-files = ${NIX_CONF_DIR}/key.private
post-build-hook = ${NIX_CONF_DIR}/cache-upload.sh
trusted-users = root ci @nixbld @ci @root

While the upload script is:

set -eu

# Allow disabling the cache upload if needed
if [[ -n "${DISABLE_NIX_CACHE_UPLOAD:-}" ]]; then
   exit 0
fi

# Disable globbing for OUT_PATHS, which may contain glob characters
set -f
export IFS=' '

echo "Uploading derivation path" $DRV_PATH
exec nix copy --to "s3://our-cache?region=us-east-2" $DRV_PATH

echo "Uploading output paths" $OUT_PATHS
exec nix copy --to "s3://our-cache?region=us-east-2" $OUT_PATHS

During the post-build-hook’s execution, we receive an error like:

building '/nix/store/hfrv3s5niccqb25mvb99arz6ki13kaly-get-revision.drv'...
post-build-hook: error: executing '/etc/nix/cache-upload.sh': Permission denied

With a trace like (expand below):

trace
error:
       … while calling the 'derivationStrict' builtin
         at <nix/derivation-internal.nix>:34:12:
           33|
           34|   strict = derivationStrict drvAttrs;
             |            ^
           35|
       … while evaluating derivation 'int-station.tar.gz'
         whose name attribute is located at /nix/store/fmxhvg8s5d3m58iv5gai6q0axpyqadrc-nixpkgs-src/pkgs/stdenv/generic/make-derivation.nix:336:7
       … while evaluating attribute 'buildCommand' of derivation 'int-station.tar.gz'
         at /nix/store/fmxhvg8s5d3m58iv5gai6q0axpyqadrc-nixpkgs-src/pkgs/build-support/trivial-builders/default.nix:59:17:
           58|         enableParallelBuilding = true;
           59|         inherit buildCommand name;
             |                 ^
           60|         passAsFile = [ "buildCommand" ]
       … while calling the 'getAttr' builtin
         at <nix/derivation-internal.nix>:44:19:
           43|       value = commonAttrs // {
           44|         outPath = builtins.getAttr outputName strict;
             |                   ^
           45|         drvPath = strict.drvPath;
       … while calling the 'derivationStrict' builtin
         at <nix/derivation-internal.nix>:34:12:
           33|
           34|   strict = derivationStrict drvAttrs;
             |            ^
           35|
       … while evaluating derivation 'stream-int-station'
         whose name attribute is located at /nix/store/fmxhvg8s5d3m58iv5gai6q0axpyqadrc-nixpkgs-src/pkgs/stdenv/generic/make-derivation.nix:336:7
       … while evaluating attribute 'conf' of derivation 'stream-int-station'
         at /nix/store/fmxhvg8s5d3m58iv5gai6q0axpyqadrc-nixpkgs-src/pkgs/build-support/docker/default.nix:1119:21:
         1118|           {
         1119|             inherit conf;
             |                     ^
         1120|             inherit (conf) imageName;
       … while calling the 'getAttr' builtin
         at <nix/derivation-internal.nix>:44:19:
           43|       value = commonAttrs // {
           44|         outPath = builtins.getAttr outputName strict;
             |                   ^
           45|         drvPath = strict.drvPath;
       … while calling the 'derivationStrict' builtin
         at <nix/derivation-internal.nix>:34:12:
           33|
           34|   strict = derivationStrict drvAttrs;
             |            ^
           35|
       … while evaluating derivation 'int-station-conf.json'
         whose name attribute is located at /nix/store/fmxhvg8s5d3m58iv5gai6q0axpyqadrc-nixpkgs-src/pkgs/stdenv/generic/make-derivation.nix:336:7
       … while evaluating attribute 'buildCommand' of derivation 'int-station-conf.json'
         at /nix/store/fmxhvg8s5d3m58iv5gai6q0axpyqadrc-nixpkgs-src/pkgs/build-support/trivial-builders/default.nix:59:17:
           58|         enableParallelBuilding = true;
           59|         inherit buildCommand name;
             |                 ^
           60|         passAsFile = [ "buildCommand" ]
       … from call site
         at /nix/store/fmxhvg8s5d3m58iv5gai6q0axpyqadrc-nixpkgs-src/pkgs/build-support/docker/default.nix:1043:26:
         1042|           paths() {
         1043|             cat $paths ${lib.concatMapStringsSep " "
             |                          ^
         1044|                            (path: "| (grep -v ${path} || true)")
       … while calling 'concatMapStringsSep'
         at /nix/store/fmxhvg8s5d3m58iv5gai6q0axpyqadrc-nixpkgs-src/lib/strings.nix:232:5:
          231|     f:
          232|     list: concatStringsSep sep (map f list);
             |     ^
          233|
       … while calling the 'concatStringsSep' builtin
         at /nix/store/fmxhvg8s5d3m58iv5gai6q0axpyqadrc-nixpkgs-src/lib/strings.nix:232:11:
          231|     f:
          232|     list: concatStringsSep sep (map f list);
             |           ^
          233|
       … while calling anonymous lambda
         at /nix/store/fmxhvg8s5d3m58iv5gai6q0axpyqadrc-nixpkgs-src/pkgs/build-support/docker/default.nix:1044:29:
         1043|             cat $paths ${lib.concatMapStringsSep " "
         1044|                            (path: "| (grep -v ${path} || true)")
             |                             ^
         1045|                            unnecessaryDrvs}
       … from call site
         at /nix/store/fmxhvg8s5d3m58iv5gai6q0axpyqadrc-nixpkgs-src/pkgs/build-support/docker/default.nix:1006:26:
         1005|         );
         1006|         overallClosure = writeText "closure" (lib.concatStringsSep " " closureRoots);
             |                          ^
         1007|
       … while calling 'writeText'
         at /nix/store/fmxhvg8s5d3m58iv5gai6q0axpyqadrc-nixpkgs-src/pkgs/build-support/trivial-builders/default.nix:130:21:
          129|   # or https://nixos.org/manual/nixpkgs/unstable/#trivial-builder-text-writing
          130|   writeText = name: text:
             |                     ^
          131|     # TODO: To fully deprecate, replace the assertion with `lib.isString` and remove the warning
       … in the condition of the assert statement
         at /nix/store/fmxhvg8s5d3m58iv5gai6q0axpyqadrc-nixpkgs-src/pkgs/build-support/trivial-builders/default.nix:132:5:
          131|     # TODO: To fully deprecate, replace the assertion with `lib.isString` and remove the warning
          132|     assert lib.assertMsg (lib.strings.isConvertibleWithToString text) ''
             |     ^
          133|       pkgs.writeText ${lib.strings.escapeNixString name}: The second argument should be a string, but it's a ${builtins.typeOf text} instead.'';
       … from call site
         at /nix/store/fmxhvg8s5d3m58iv5gai6q0axpyqadrc-nixpkgs-src/pkgs/build-support/trivial-builders/default.nix:132:12:
          131|     # TODO: To fully deprecate, replace the assertion with `lib.isString` and remove the warning
          132|     assert lib.assertMsg (lib.strings.isConvertibleWithToString text) ''
             |            ^
          133|       pkgs.writeText ${lib.strings.escapeNixString name}: The second argument should be a string, but it's a ${builtins.typeOf text} instead.'';
       … while calling 'assertMsg'
         at /nix/store/fmxhvg8s5d3m58iv5gai6q0axpyqadrc-nixpkgs-src/lib/asserts.nix:39:21:
           38|   # TODO(Profpatsch): add tests that check stderr
           39|   assertMsg = pred: msg: pred || builtins.throw msg;
             |                     ^
           40|
       … in the left operand of the OR (||) operator
         at /nix/store/fmxhvg8s5d3m58iv5gai6q0axpyqadrc-nixpkgs-src/lib/asserts.nix:39:31:
           38|   # TODO(Profpatsch): add tests that check stderr
           39|   assertMsg = pred: msg: pred || builtins.throw msg;
             |                               ^
           40|
       … from call site
         at /nix/store/fmxhvg8s5d3m58iv5gai6q0axpyqadrc-nixpkgs-src/pkgs/build-support/trivial-builders/default.nix:132:27:
          131|     # TODO: To fully deprecate, replace the assertion with `lib.isString` and remove the warning
          132|     assert lib.assertMsg (lib.strings.isConvertibleWithToString text) ''
             |                           ^
          133|       pkgs.writeText ${lib.strings.escapeNixString name}: The second argument should be a string, but it's a ${builtins.typeOf text} instead.'';
       … while calling anonymous lambda
         at /nix/store/fmxhvg8s5d3m58iv5gai6q0axpyqadrc-nixpkgs-src/lib/strings.nix:2298:6:
         2297|     types = [ "null" "int" "float" "bool" ];
         2298|   in x:
             |      ^
         2299|     isStringLike x ||
       … in the left operand of the OR (||) operator
         at /nix/store/fmxhvg8s5d3m58iv5gai6q0axpyqadrc-nixpkgs-src/lib/strings.nix:2300:27:
         2299|     isStringLike x ||
         2300|     elem (typeOf x) types ||
             |                           ^
         2301|     (isList x && lib.all isConvertibleWithToString x);
       … in the left operand of the OR (||) operator
         at /nix/store/fmxhvg8s5d3m58iv5gai6q0axpyqadrc-nixpkgs-src/lib/strings.nix:2299:20:
         2298|   in x:
         2299|     isStringLike x ||
             |                    ^
         2300|     elem (typeOf x) types ||
       … from call site
         at /nix/store/fmxhvg8s5d3m58iv5gai6q0axpyqadrc-nixpkgs-src/lib/strings.nix:2299:5:
         2298|   in x:
         2299|     isStringLike x ||
             |     ^
         2300|     elem (typeOf x) types ||
       … while calling 'isStringLike'
         at /nix/store/fmxhvg8s5d3m58iv5gai6q0axpyqadrc-nixpkgs-src/lib/strings.nix:2322:18:
         2321|   */
         2322|   isStringLike = x:
             |                  ^
         2323|     isString x ||
       … in the left operand of the OR (||) operator
         at /nix/store/fmxhvg8s5d3m58iv5gai6q0axpyqadrc-nixpkgs-src/lib/strings.nix:2325:17:
         2324|     isPath x ||
         2325|     x ? outPath ||
             |                 ^
         2326|     x ? __toString;
       … in the left operand of the OR (||) operator
         at /nix/store/fmxhvg8s5d3m58iv5gai6q0axpyqadrc-nixpkgs-src/lib/strings.nix:2324:14:
         2323|     isString x ||
         2324|     isPath x ||
             |              ^
         2325|     x ? outPath ||
       … in the left operand of the OR (||) operator
         at /nix/store/fmxhvg8s5d3m58iv5gai6q0axpyqadrc-nixpkgs-src/lib/strings.nix:2323:16:
         2322|   isStringLike = x:
         2323|     isString x ||
             |                ^
         2324|     isPath x ||
       … while calling the 'isString' builtin
         at /nix/store/fmxhvg8s5d3m58iv5gai6q0axpyqadrc-nixpkgs-src/lib/strings.nix:2323:5:
         2322|   isStringLike = x:
         2323|     isString x ||
             |     ^
         2324|     isPath x ||
       … while calling the 'concatStringsSep' builtin
         at /nix/store/fmxhvg8s5d3m58iv5gai6q0axpyqadrc-nixpkgs-src/pkgs/build-support/docker/default.nix:1006:47:
         1005|         );
         1006|         overallClosure = writeText "closure" (lib.concatStringsSep " " closureRoots);
             |                                               ^
         1007|
       … while calling the 'getAttr' builtin
         at <nix/derivation-internal.nix>:44:19:
           43|       value = commonAttrs // {
           44|         outPath = builtins.getAttr outputName strict;
             |                   ^
           45|         drvPath = strict.drvPath;
       … while calling the 'derivationStrict' builtin
         at <nix/derivation-internal.nix>:34:12:
           33|
           34|   strict = derivationStrict drvAttrs;
             |            ^
           35|
       … while evaluating derivation 'int-station-customisation-layer'
         whose name attribute is located at /nix/store/fmxhvg8s5d3m58iv5gai6q0axpyqadrc-nixpkgs-src/pkgs/stdenv/generic/make-derivation.nix:336:7
       … while evaluating attribute 'paths' of derivation 'int-station-customisation-layer'
         at /nix/store/fmxhvg8s5d3m58iv5gai6q0axpyqadrc-nixpkgs-src/pkgs/build-support/docker/default.nix:957:11:
          956|           name = "${baseName}-customisation-layer";
          957|           paths = contentsList;
             |           ^
          958|           extraCommands =
       … while calling the 'getAttr' builtin
         at <nix/derivation-internal.nix>:44:19:
           43|       value = commonAttrs // {
           44|         outPath = builtins.getAttr outputName strict;
             |                   ^
           45|         drvPath = strict.drvPath;
       … while calling the 'derivationStrict' builtin
         at <nix/derivation-internal.nix>:34:12:
           33|
           34|   strict = derivationStrict drvAttrs;
             |            ^
           35|
       … while evaluating the derivation attribute 'name'
         at /nix/store/fmxhvg8s5d3m58iv5gai6q0axpyqadrc-nixpkgs-src/pkgs/stdenv/generic/make-derivation.nix:336:7:
          335|     // (optionalAttrs (attrs ? name || (attrs ? pname && attrs ? version)) {
          336|       name =
             |       ^
          337|         let
       … while evaluating the `name` attribute passed to builtins.derivationStrict
       … from call site
         at /nix/store/fmxhvg8s5d3m58iv5gai6q0axpyqadrc-nixpkgs-src/pkgs/stdenv/generic/make-derivation.nix:354:9:
          353|         in
          354|         lib.strings.sanitizeDerivationName (
             |         ^
          355|           if attrs ? name
       … while calling anonymous lambda
         at /nix/store/fmxhvg8s5d3m58iv5gai6q0axpyqadrc-nixpkgs-src/lib/strings.nix:2625:3:
         2624|   in
         2625|   string:
             |   ^
         2626|   # First detect the common case of already valid strings, to speed those up
       … while evaluating a branch condition
         at /nix/store/fmxhvg8s5d3m58iv5gai6q0axpyqadrc-nixpkgs-src/lib/strings.nix:2627:3:
         2626|   # First detect the common case of already valid strings, to speed those up
         2627|   if stringLength string <= 207 && okRegex string != null
             |   ^
         2628|   then unsafeDiscardStringContext string
       … in the left operand of the AND (&&) operator
         at /nix/store/fmxhvg8s5d3m58iv5gai6q0axpyqadrc-nixpkgs-src/lib/strings.nix:2627:33:
         2626|   # First detect the common case of already valid strings, to speed those up
         2627|   if stringLength string <= 207 && okRegex string != null
             |                                 ^
         2628|   then unsafeDiscardStringContext string
       … in the argument of the not operator
         at /nix/store/fmxhvg8s5d3m58iv5gai6q0axpyqadrc-nixpkgs-src/lib/strings.nix:2627:26:
         2626|   # First detect the common case of already valid strings, to speed those up
         2627|   if stringLength string <= 207 && okRegex string != null
             |                          ^
         2628|   then unsafeDiscardStringContext string
       … while calling the 'lessThan' builtin
         at /nix/store/fmxhvg8s5d3m58iv5gai6q0axpyqadrc-nixpkgs-src/lib/strings.nix:2627:26:
         2626|   # First detect the common case of already valid strings, to speed those up
         2627|   if stringLength string <= 207 && okRegex string != null
             |                          ^
         2628|   then unsafeDiscardStringContext string
       … while calling the 'stringLength' builtin
         at /nix/store/fmxhvg8s5d3m58iv5gai6q0axpyqadrc-nixpkgs-src/lib/strings.nix:2627:6:
         2626|   # First detect the common case of already valid strings, to speed those up
         2627|   if stringLength string <= 207 && okRegex string != null
             |      ^
         2628|   then unsafeDiscardStringContext string
       … in the condition of the assert statement
         at /nix/store/fmxhvg8s5d3m58iv5gai6q0axpyqadrc-nixpkgs-src/pkgs/stdenv/generic/make-derivation.nix:359:13:
          358|             # we cannot coerce null to a string below
          359|             assert assertMsg (attrs ? version && attrs.version != null) "The `version` attribute cannot be null.";
             |             ^
          360|             "${attrs.pname}${staticMarker}${hostSuffix}-${attrs.version}"
       … from call site
         at /nix/store/fmxhvg8s5d3m58iv5gai6q0axpyqadrc-nixpkgs-src/pkgs/stdenv/generic/make-derivation.nix:359:20:
          358|             # we cannot coerce null to a string below
          359|             assert assertMsg (attrs ? version && attrs.version != null) "The `version` attribute cannot be null.";
             |                    ^
          360|             "${attrs.pname}${staticMarker}${hostSuffix}-${attrs.version}"
       … while calling 'assertMsg'
         at /nix/store/fmxhvg8s5d3m58iv5gai6q0axpyqadrc-nixpkgs-src/lib/asserts.nix:39:21:
           38|   # TODO(Profpatsch): add tests that check stderr
           39|   assertMsg = pred: msg: pred || builtins.throw msg;
             |                     ^
           40|
       … in the left operand of the OR (||) operator
         at /nix/store/fmxhvg8s5d3m58iv5gai6q0axpyqadrc-nixpkgs-src/lib/asserts.nix:39:31:
           38|   # TODO(Profpatsch): add tests that check stderr
           39|   assertMsg = pred: msg: pred || builtins.throw msg;
             |                               ^
           40|
       … in the right operand of the AND (&&) operator
         at /nix/store/fmxhvg8s5d3m58iv5gai6q0axpyqadrc-nixpkgs-src/pkgs/stdenv/generic/make-derivation.nix:359:47:
          358|             # we cannot coerce null to a string below
          359|             assert assertMsg (attrs ? version && attrs.version != null) "The `version` attribute cannot be null.";
             |                                               ^
          360|             "${attrs.pname}${staticMarker}${hostSuffix}-${attrs.version}"
       … while evaluating the attribute 'version'
         at /builds/spectrust-inc/package/default.nix:58:3:
           57|   # version = # builtins.readFile revision;
           58|   version = "${revision}";
             |   ^
           59|
       … while calling the 'readFile' builtin
         at /builds/spectrust-inc/package/default.nix:46:14:
           45|   # Create a derivation file containing the current git revision.
           46|   revision = builtins.readFile specLib.getGitRevision;
             |              ^
           47|
       … while realising the context of path '/nix/store/w8bfgjiswicdmm14aqfk6qvb7x63vhbc-get-revision'
       error: program '/etc/nix/spec-nix-cache-upload.sh' failed with exit code 1

I cannot figure out why the normal build would be fine, but the post-build script would fail. It seems to fail on any readFile invocation: I commented out the one in the trace, but it still fails on an internal one that it hits after that.

If I comment out post-build-hook in the nix.conf, the build itself works fine. We obviously just don’t upload anything to the cache in that case.

Any ideas would be appreciated.

nix version: nix (Nix) 2.24.11

Wow folks I’m just an idiot. Forgot to mark the script as executable, was misled by the trace.

Don’t forget your chmod +x on your post-build-hook :slight_smile: