Перейти к содержимому
← Все заметки

// заметки

nix-direnv: «minimum required bash version is 4.4»

nix-direnv падает с «minimum required bash version is 4.4», хотя bash новее. Разбираю вшитые пути /nix/store, почему текст врёт и как это исправить

Опубликовано: 2026-10-084 мин чтения
Nixnix-direnvdirenvLinux

Контекст: rootless Nix, direnv, nix-direnv и проект с use flake. nix-direnv может отказаться собирать devShell с сообщением про минимальную версию bash, даже когда bash явно новее. Причина не в bash: собранный через resholve (инструмент, заменяющий вызовы утилит абсолютными путями) nix-direnv хранит вшитые пути в /nix/store, а снаружи mount-namespace (изолированного окружения, в котором смонтирован /nix) этих путей не существует. Ниже: как это диагностировать и почему правильный фикс не «отключить проверку», а запускать direnv внутри namespace

Симптом

После правки .envrc или очистки кеша окружение перестаёт собираться:

direnv: using flake
direnv: nix-direnv: cache invalidated: profile (…/.direnv/flake-profile-…) does not exist
direnv: nix-direnv: minimum required bash version is 4.4 (installed: 5.3.15)
direnv: nix-direnv: Evaluating current devShell failed. Falling back to previous environment!

Установлен bash 5.3.15, то есть версия заведомо выше требуемой. При этом dotenv-часть .envrc работает: переменные из .env подхватываются. Падает именно сборка flake

Почему сообщение врёт

Сообщение про bash это не причина, а следствие. Проверка версий в nix-direnv устроена так: функция _require_version сравнивает требуемую и установленную версии через printf ... | LC_ALL=C sort -c -V. Если sort недоступен по вшитому пути, пайп падает, условие считается невыполненным, и печатается сообщение, которое собирается в рантайме:

minimum required $(basename "$cmd") version is $required (installed: $raw_version)

resholve переписывает на абсолютные store-пути обе утилиты из этого шаблона: и sort в условии, и basename, которым подставляется имя команды. В одном пакете они вшиты в один каталог coreutils, поэтому их доступность обычно совпадает, но текст собирается из двух независимых команд, и итоговая строка зависит от того, какие пути резолвятся:

  • если basename отрабатывает, слово на месте: minimum required bash version is 4.4 (installed: 5.3.15), так выглядит лог в начале заметки
  • если недоступны оба пути, слово пропадает: minimum required version is 4.4 (installed: 5.3.15) с двойным пробелом, а в stderr попадает basename: No such file or directory

Причина в обоих случаях одна: недоступные вшитые пути sort и basename, а не старая версия bash. Проверка bash идёт первой, поэтому сообщение и говорит про bash

Ошибка указывает не на ту причину, которая сломалась

Причина: вшитые пути nix-direnv

nix-direnv 3.2.0 собран через resholve. Это инструмент, который в скриптах заменяет вызовы утилит на абсолютные пути в /nix/store. В результате внутри direnvrc (файла из пакета, share/nix-direnv/direnvrc, а не пользовательского конфига) оказываются зашиты конкретные сборки:

/nix/store/<hash>-coreutils-9.11/bin/{basename,cat,chmod,mkdir,mktemp,mv,realpath,rm,sha1sum,sort,touch}
/nix/store/<hash>-nix-2.34.8/bin/nix

Для Nix это норма: хеш в пути фиксирует точную версию пакета. Проблема в другом: эти пути существуют только внутри mount-namespace, который поднимает rootless Nix. Снаружи каталога /nix нет, значит нет ни вшитых sort и basename, ни nix. sort роняет сравнение версий, а basename не даёт подставить имя команды в текст сообщения, поэтому проверка и падает

Решение

Правильный фикс: запускать direnv целиком внутри namespace, тогда вшитые пути валидны и проверка проходит. Минимальный пример обёртки:

#!/usr/bin/env bash
# $HOME/.local/bin/nix-enter-direnv: запустить direnv внутри namespace
exec "$HOME/.local/bin/nix-enter" direnv "$@"

Здесь nix-enter поднимает namespace, а direnv выполняется уже внутри него. Полный код namespace-aware обёртки над nix разобран в заметке «unshare failed из-за вложенного namespace»

Страховка на время диагностики: переменная NIX_DIRENV_SKIP_VERSION_CHECK=1 отключает проверки версий

export NIX_DIRENV_SKIP_VERSION_CHECK=1
direnv allow

Если с ней окружение собирается, а без неё падает с сообщением про bash, дело именно в проверке версий

Важно: поведение зависит от источника установки. В сборке из репозитория nix-community (default.nix) NIX_DIRENV_SKIP_VERSION_CHECK=1 выставляется автоматически в прологе, а в nixpkgs-версии этого нет. Если у вас пакет из nixpkgs, проверка версий активна, и вшитые пути имеют значение

Проверка

Найти вшитые пути в direnvrc и убедиться, что снаружи они не резолвятся:

# какие абсолютные пути зашиты в nix-direnv
grep -o "/nix/store/[^ \"']*" "$HOME"/.nix/store/*-nix-direnv-*/share/nix-direnv/direnvrc | sort -u

# внутри ли мы namespace
test -d /nix/store && echo inside || echo outside

Первый grep покажет, какие утилиты ожидаются по абсолютным путям. Второй тест: единственный надёжный маркер namespace. Не полагайтесь на $NIX_STORE_DIR, её можно выставить и снаружи

Дополнительно: убедиться, что nix-direnv не удалит сборщик мусора, потому что он лежит в профиле (nix profile install)

nix-store --gc --print-dead | grep nix-direnv

Пустой вывод означает, что пакет не помечен к удалению

Подводные камни

Отключение проверки версий не решение: NIX_DIRENV_SKIP_VERSION_CHECK=1 прячет симптом, но не чинит вшитые пути

Пути из devShell снаружи namespace молча не работают: оболочка пропускает недоступные записи PATH и берёт системные бинари. Подробнее: «Nix devShell теряет пакеты в fish»

Хеши в путях не секрет и не проблема сами по себе, но они привязывают скрипт к конкретным сборкам. Обновление пакета меняет хеш, путь становится другим, поэтому «у меня этот путь работает» не доказывает, что он сработает в чужой системе

Итог

Сообщение minimum required bash version is 4.4 означает не старый bash, а неразрешённые вшитые пути nix-direnv. Надёжное решение: запускать direnv внутри namespace. NIX_DIRENV_SKIP_VERSION_CHECK=1 годится только для подтверждения диагноза

Нужно решить такую задачу на вашем проекте?