// заметки
nix-direnv: «minimum required bash version is 4.4»
nix-direnv падает с «minimum required bash version is 4.4», хотя bash новее. Разбираю вшитые пути /nix/store, почему текст врёт и как это исправить
Контекст: 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 годится только для подтверждения диагноза
Похожие заметки
nix-direnv: «unshare failed: Операция не позволена»
direnv с use flake падает на unshare failed, хотя dotenv работает. Разбираю причину (вложенный user-namespace) и показываю фикс обёртки над nix
Nix devShell в fish: PATH молча не подхватывается
Пакеты из Nix devShell не подхватываются в fish: PATH указывает внутрь mount-namespace. Диагностика молчаливой подмены и прозрачный re-exec оболочки
Кто вызывает unshare: перехват бинаря через PATH
Команда падает и не называет виновника. Показываю приём: подменить бинарь логирующим скриптом через PATH и найти вызывающего без трассировки и прав root