Всем привет! В этой статье хочу рассказать об одном интересном (по крайней мере для меня) случае, который произошел со мной на прошлой неделе.
Посреди дня поступила новая задача. Я с охотой принялся вайбкодить. Для тасок такого рода довольно обычный флоу работы: верстка форм по макету, добавление новых полей в Zod схему, обновление JSX, вывод типов из Zod схемы, etc.
В процессе работы замечаю, что VSCode начал лагать: не работал вывод типов, автокомплит и подсказки.

Я уже замечал подобное около полугода назад, и в тот момент помогло отключение расширения GitHub Copilot (нашел ишью на гитхабе).
Сейчас я опять подумал на проблему с расширением и начал искать ее. Для дебага проблем в VSCode есть инструмент, который запускается через Help > Start Extension Bisect.
Он поочередно дизейблит расширения и спрашивает, воспроизводится баг или нет. Таким способом подозрения пали на расширение гитлаба. Забизейблив его, ситуация улучшилась. Вывод типов появился и автокомплит тоже.
Однако через пару часов ситуация ухудшилась. Поискав ишью на гитхабе об этой проблеме (неработающий по‑сути vscode), я ничего не нашел.
Попробовал ребутнуть комп — помогло: vscode снова заработал. «Ура!» — подумал я.
Но спустя какое‑то время ситуация повторилась. Это был вечер пятницы, конец рабочего дня. «Ладно, может, какая‑то проблема с антивирусами или еще с каким‑то корпоративным ПО, которое установлено на ноуте», — подумал я. Вернусь в понедельник, и все заработает.
ПОНЕДЕЛЬНИК
Как полагается, проблема осталась. Ребут снова помог временно. После этого вдобавок начал лагать уже и хром.
В середине рабочего дня пользоваться ноутом уже стало невыносимо. Решил открыть «Мониторинг системы». Открываю «Память» и офигеваю. При запущенных на компе Spotify, Chrome, VSCode и Time используется 28 гигов оперативы из 32 доступных!
Начинаю смотреть, что там за процессы такие: все процессы хрома в сумме занимают 6 ГБ — много, конечно, но ок. Смотрю дальше и вижу полотно запущенных node процессов. Каждый из них занимает ~700 мб оперативы. «Что за процесс такой?» — думаю я. Открываю трейс и вижу, что запущена команда вида:
npm exec tsc --project /Users/Developer/projects/front/node_modules/.federation/tsconfig.bc0bb64cb574b1df5f6fb9bf2cef34a9.json (94412)
Она висит уже 13 минут и не может завершиться. И таких команд висят десятки:

Смотрю инициатора: какая‑то либа из node_modules. По итогу понимаю: у меня запущен vite dev‑сервер, и в нем либа @module‑federation примерно раз в 5 мин запускает генерацию контракта для микрофронта. За час получается 12 висячих node процессов.
Окей, то, что либа генерит какой‑то неиспользуемый контракт в папку, неиндексируемую гитом, — это ок, но почему генерация контракта выполняется так долго и по сути вообще не заканчивается?
Ранее этим же днем, давая простые таски клоду, я замечал, что он выполняет их очень долго, по 30–60 минут: задачи, которые делать клоду 2 мин от силы. Заметил висячую уже 20 минут shell команду npm run check. «Что‑то странное», — подумал я и просто отменил ее.
По логике — если генерация d.ts контракта выполняется слишком долго, то логично попробовать найти ошибку в типах. Это я и начал делать. Однако явных проблем касаемо типов я не заметил.
Тогда на ум приходит посмотреть всю цепочку того, как TS собирает контракт. Я запустил ту же самую команду с флагом --generateTrace и заметил, что компилятор упёрся в предел глубины инстанцирования типов и продолжал пересчитывать одну и ту же пару типов больше 500 000 раз подряд, не завершаясь:

И происходит это на одном конкретном файле — назовем его useSomeForm.tsx. На первый взгляд ничего подозрительно — в нем описывается форма в связке React Hook Form + Zod. При детальном рассмотрении замечаю корень проблемы:
// useSomeForm.tsx const getBaseSchema = () => { return z.object({ // fields here }) } export type BaseSchemaInferred = ReturnType<typeof getBaseSchema>;
В другом файле:
// SomeForm.tsx const { control } = useFormContext<BaseSchemaInferred>();
То есть получалась такая цепочка: React Hook Form под капотом рекурсивно строит тип Path (все возможные строки‑пути до полей формы), обходя каждое поле T. Когда вместо простого объекта я ему подставил весь внутренний тип Zod‑схемы (с её union'ами.or(),.extend() и собственной рекурсивной generic‑структурой), два рекурсивных generic‑механизма (zod ↔ React Hook Form) начинали бесконечно вызывать друг друга.
Корректный вариант должен был выглядеть следующим образом:
export type BaseSchemaInferred = z.infer<BaseSchema>;
В большом файле, который описывает множество полей формы с валидацией и наследованием, такую ошибку было сложно заметить.
Подводя итог, хочется сделать вывод о том, что нужно быть внимательнее и вдумчивее читать тот код, который пишет ИИ.
Спасибо за внимание:‑)
yarkov
В пятницу мне клод очень похожую цепочку сам нашёл и распутал. И тайпчек стал вместо 10-15 минут проходить максимум за минуту.