Всем привет! В этой статье хочу рассказать об одном интересном (по крайней мере для меня) случае, который произошел со мной на прошлой неделе.

Посреди дня поступила новая задача. Я с охотой принялся вайбкодить. Для тасок такого рода довольно обычный флоу работы: верстка форм по макету, добавление новых полей в 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>;

В большом файле, который описывает множество полей формы с валидацией и наследованием, такую ошибку было сложно заметить.

Подводя итог, хочется сделать вывод о том, что нужно быть внимательнее и вдумчивее читать тот код, который пишет ИИ.

Спасибо за внимание:‑)

Комментарии (1)


  1. yarkov
    08.11.2026 18:52

    В пятницу мне клод очень похожую цепочку сам нашёл и распутал. И тайпчек стал вместо 10-15 минут проходить максимум за минуту.