Мой PR в Bun пережил язык, на котором был написан
Продолжение истории: мой фикс на Zig для bun create закрыл бот — потому что Bun переехал на Rust. Я разобрался, что случилось на самом деле, переписал фикс на Rust, повоевал с тулчейном на NixOS и упёрся в настоящее узкое место Bun — не в код, а в ревью.
Это продолжение поста Как сломанная команда Nuxt привела к багу в Bun CLI.
Тот пост закончился открытым PR в Bun. К тому моменту я нашёл настоящий баг —
bun create <template> -- <args> передавал дальше сам разделитель --, вместо
того чтобы его проглотить, — свёл его к маленькому примеру, завёл
oven-sh/bun#29087 и отправил
исправление на Zig:
oven-sh/bun#29089.
А потом PR закрыли. Не человек — бот. И причина, которую он назвал, оказалась интереснее самого бага.
PR закрыл бот
Бот закрыл его автоматически с комментарием, который, если пересказать своими словами, звучал так:
Закрываю как устаревший: этот PR появился ещё до перехода на Rust. Все файлы в
src/, которые он меняет, с тех пор удалены или перенесены вmain.
Сначала мне показалось, что это чушь. Я открыл свою локальную копию репозитория —
и src/cli.zig, единственный файл, который трогал мой PR, был на месте. Никакого
Rust и в помине. Bun написан на Zig. Бот явно что-то выдумал.
Только вот нет. Моя ветка main отставала на сотни коммитов. Когда я подтянул
настоящий, свежий main, файла src/cli.zig там уже не было.
Теперь Bun написан на Rust
Вот что я полностью прозевал: Bun переписывает своё ядро с Zig на Rust. В текущем
main (и в canary-сборках) собирается и поставляется именно Rust-версия.
Стоило присмотреться — и следы были по всему репозиторию:
src/cliтеперь стал симлинком наruntime/cli;- логика
bun create, которую я правил на Zig, переехала в Rust — вsrc/runtime/cli/mod.rs,create_command.rsиbunx_command.rs; - вся Rust-часть — это один Cargo-воркспейс примерно из двухсот крейтов;
- появились файлы
.rs,clippy.tomlи целые скриптыclippy-loopтам, где раньше жил Zig.
То есть прав был бот, а не я. Мой PR нельзя было ни перебазировать, ни переоткрыть по-человечески — файла, который он редактировал, больше не существовало. Вывод дошёл моментально: сначала подтяни апстрим, потом доверяй своей рабочей копии. Я спорил с ботом, держа в голове картину мира многомесячной давности.
Баг пережил переписывание
И вот тут повезло. Когда код переписывают, баги переезжают вместе с ним.
Я открыл новую логику передачи аргументов в src/runtime/cli/mod.rs — и она
повторяла старый Zig один в один: тот же цикл, который отправлял в bunx всё
подряд после имени шаблона, включая ведущий --. Переписывание аккуратно
перенесло баг через границу языка.
Значит, исправление по-прежнему нужно — просто на другом языке. Я переписал его на Rust:
// убрать один ведущий `--`; проглотить `--bun` перед ним;// сохранить второй `--` и любой `--bun` после разделителя как обычные аргументыПоведение ровно такое же, как в исходном фиксе на Zig:
- убирает один ведущий
--перед передачей аргументов в create-скрипт; - проглатывает собственный флаг обёртки
--bun, если он стоит до разделителя; - сохраняет второй
--и--bunпосле разделителя как обычные аргументы.
Регрессионный тест из старого PR переехал почти без правок — он поднимает
локальный реестр пакетов, запускает bun create против пакета, чей бинарник
печатает свои аргументы, и проверяет все варианты с разделителем. Новый PR здесь:
oven-sh/bun#32954.
Собрать Bun на NixOS — отдельное приключение
В первом посте я отделался расплывчатым «проблемы с тулчейном на NixOS». В этот раз я честно попытался собрать проект и прогнать тест — и упёрся в стену в трёх разных местах. Записываю, потому что любой на NixOS наступит на те же грабли:
- zstd. Отладочная сборка идёт с
-gz=zstd(отладочные секции, сжатые zstd), ноclang_21из nixpkgs собран без поддержки zstd. Везде это предупреждение — кроме предкомпилированного заголовка, где включён-Werror, так что там оно становится ошибкой. Помогает переключение на-gz=zlib. _FORTIFY_SOURCE. Обёртка компилятора в Nix насильно добавляет-D_FORTIFY_SOURCE=2, но отладочная сборка идёт с-O0, а fortify требует оптимизации. Получается предупреждение, которое-Werrorснова превращает в ошибку. Лечится исключениемfortifyизNIX_HARDENING_ENABLE.- Nightly-версия Rust. Вот это быстро обойти не вышло. Bun жёстко фиксирует
конкретную nightly-сборку Rust и использует
-Zbuild-std. А у меня на машине был только стабильныйcargo/rustcиз Nix и не былоrustup, чтобы поставить нужный nightly. Си-плюс-плюсная половина собралась, а Rust-овая — нет.
Так что собрать Bun целиком локально я снова не смог. Я прямо написал об этом в описании PR и положился на CI. Уж лучше отправить PR с честным «локально прогнать не смог, вот почему», чем делать вид, будто был зелёный прогон, которого не было.
Автоматическое ревью
CI у Bun запускает на каждый PR автоматическое ревью (CodeRabbit). Бот оставил несколько замечаний — и вот тут, по-моему, и начинается самое интересное, потому что слепо применять всё, что говорит бот, не стоит.
Два замечания были по делу: подрезать слишком длинный комментарий и проверять код возврата подпроцесса в тесте, чтобы тот не проходил молча при ненулевом выходе. Оба взял.
Одно отклонил. Бот утверждал, что обёртка должна обрабатывать -b (короткую форму
--bun) так же, как --bun. Я проверил: -b и правда настоящий псевдоним в
bunx. Но сделать это правильно значило перелопатить существующую логику разбора
аргументов, которую мой патч вообще не трогал; к тому же это выходило за рамки
исходной задачи, а -b и так работает — bunx понимает его дальше по цепочке.
Поэтому я ответил с объяснением и предложил вынести это в отдельный PR. Даже сам
промпт CodeRabbit говорит: «чини только то, что действительно осталось багом,
остальное пропусти с коротким объяснением». Ровно этот же инстинкт стоит применять
к любой автоматической подсказке — в том числе к тем, что я пишу с Claude
Code.
Настоящее узкое место — не код
А теперь неприятная часть. Само исправление маленькое. Сложность — добиться, чтобы на него посмотрел живой человек.
Поскольку мой PR из форка, CI на нём даже не запускается автоматически — сначала его должен разблокировать мейнтейнер. И это ещё мягкий барьер. Если провести немного времени в Discord-канале Bun, замечаешь повторяющийся сюжет: у внешних контрибьюторов корректные PR, которые один раз отревьюили и где уже внесли правки, висят месяцами. Восемь месяцев. Девять. Люди пишут мейнтейнерам на почту, отмечают их на GitHub, раз в пару недель напоминают о себе в Discord — а в ответ только активность ботов.
Это не злой умысел, и я хочу быть честным: Bun делает очень маленькая команда,
которая движется крайне быстро, а переписать весь рантайм с Zig на Rust — это
огромная работа, которая по понятным причинам съедает все силы на проверку чужого
кода. Основатель проекта по-прежнему коммитит каждый день. Но для внешнего
контрибьютора сейчас всё выглядит так: с твоим PR взаимодействует автоматика —
robobun и компания, — а внутренние задачи (вполне разумно) идут вперёд очереди
из community-исправлений.
Если соберёшься контрибьютить в Bun сегодня — иди туда, держа это в голове. Это сильно меняет подход.
Как сейчас реально контрибьютить в Bun
Что я сказал бы себе перед стартом:
- Целься в Rust-код, а не в Zig. Рантайм, который поставляется, лежит в
src/как Cargo-воркспейс.bun createживёт вsrc/runtime/cli/. Сначала найди тот слой, который реально компилируется, а уже потом пиши хоть строчку. - Всегда сначала подтягивай апстрим.
mainдвижется так быстро, что недельной давности копия может оказаться уже другим кодом. Я выучил это, споря с ботом. - Своди к минимальному примеру. Крошечный пакет, печатающий свои аргументы, сделал баг неоспоримым, а исправление — очевидным. Когда ревью в дефиците, это важно вдвойне: проверяющий должен убедиться в проблеме за тридцать секунд.
- Всегда добавляй регрессионный тест. Это разница между «поверь мне» и «вот доказательство, и доказательство, что баг не вернётся».
- Честно пиши в PR о том, что не смог проверить. Я не собрал проект локально — и так и написал. Проверяющему это полезнее ложной уверенности.
- Закладывай задержку и напоминай о себе вежливо. Понятное описание PR, связанные задачи и редкое уважительное напоминание — это всё, что в твоих силах. Не спамь.
- Используй ИИ как штурмана, а не как оракула. Claude Code реально помог перенести готовое исправление на язык, на котором я не пишу каждый день, — но только после того, как я сам понял суть. Он не заменил понимание задачи и не должен его заменять.
Чем всё закончилось (пока)
Исправление теперь написано дважды, на двух разных языках, ради одного и того же
однострочного поведения: bun create должен убирать первый -- перед передачей
аргументов дальше. Rust-PR открыт. CI заблокирован и ждёт мейнтейнера. Три задачи,
которые он закрывает (#29087,
#20314,
#6566), всё ещё открыты.
Бот был прав, что мой PR появился до переписывания. Он только не уточнил, что баг появился ещё раньше — и пережил его. Так что я переписал исправление на Rust и снова встал в очередь.
Теперь, как и все, жду.