Claude Code на Windows: что я прописал агенту, чтобы он перестал спотыкаться

Большая часть материалов про Claude Code написана под macOS и Linux. У меня часть задач завязана на Windows, в первую очередь браузерная автоматизация с живыми профилями Chrome, поэтому агент работает прямо в нативном окружении, без WSL. Сам Claude Code под Windows ведёт себя стабильно. Проблемы возникают на стыке с окружением: PowerShell 5.1, кодировки, имена процессов, PATH.
Каждую такую мелочь я в итоге записывал агенту в память, чтобы он больше не наступал туда же. Набралось пять штук, плюс блок для CLAUDE.md в конце, который можно забрать себе целиком. Если вы тоже на Windows и без WSL, возможно, это сэкономит вам пару вечеров.

1. PowerShell тут 5.1, а модель думает, что 7
На Windows у агента два шелла: Git Bash и PowerShell. С PowerShell первая засада: это не семёрка, а 5.1, тот, что идёт вместе с системой. Модель же, судя по всему, выросла на bash и современном pwsh, и регулярно пишет вот так:

&& и || в 5.1 просто нет. Нет и ?:, и ??. Агент получает ParserError, переписывает команду, иногда снова с &&. В 5.1 это пишется так:
npm run build; if ($?) { npm test }
Вторая засада хитрее. Если повесить 2>&1 на внешнюю программу (git, npm, node), PowerShell 5.1 заворачивает каждую строку stderr в ErrorRecord, и $? становится $false, даже когда программа вышла с кодом 0. Git, например, пишет прогресс в stderr. Агент видит красный текст, решает, что всё сломалось, и начинает чинить то, что работает. Лечится просто: не делать 2>&1 на exe и смотреть на $LASTEXITCODE.
Третья - кодировки. Set-Content по умолчанию пишет в системной ANSI-кодировке, и кириллица в конфиге превращается в кракозябры. Out-File пишет UTF-8, но с BOM, а BOM ломает часть парсеров JSON. Я запретил агенту писать файлы через шелл вообще: у него есть свой инструмент записи, пусть пользуется им. Если уж через PowerShell, то только с явным -Encoding utf8.
И мелочь про Git Bash: он иногда падает и оставляет в рабочей папке файлы вида *.stackdump. Прямо сейчас у меня в git status висит du.exe.stackdump, о котором я не просил. Если не следить, такое уезжает в коммит, так что *.stackdump стоит сразу добавить в глобальный gitignore.
2. Python из Microsoft Store называется не python.exe
Самая неприятная история из всех. Я попросил агента остановить скрипт. Агент поискал процесс python, не нашёл и честно отчитался: всё остановлено. А скрипт продолжал работать.
Python у меня из Microsoft Store, и в списке процессов он виден как python3.10.exe. Закончилось тем, что параллельно работали два экземпляра скрипта и открыли один и тот же профиль Chrome: вкладка закрывалась прямо под скриптом, лишние окна плодились.

Правило теперь такое: процессы искать по командной строке, а не по имени, и после остановки проверять, что ничего не осталось.
Get-CimInstance Win32_Process |
Where-Object { $_.CommandLine -like '*my_script*' } |
Select-Object ProcessId, Name, CommandLine
3. У агента свой PATH
В моём терминале go есть. У агента его нет: ни в его Bash, ни в его PowerShell. Окружение агента и моё окружение - не одно и то же, и это надо держать в голове каждый раз, когда он говорит «команда не найдена».
Чинить окружение я не стал. Записал в память полный путь и разрешил в настройках именно его:
& "C:\Program Files\Go\bin\go.exe" -C .\backend test ./...
Отдельный анекдот: в авторежиме go test по этому пути проходит, а go vet тем же путём классификатор блокирует. Логики я так и не нашёл, просто живу с этим.
4. localhost и 127.0.0.1 - не одно и то же
Строго говоря, это не про Windows, но времени ушло не меньше, чем на остальное.
Next.js 16 в dev-режиме блокирует HMR-вебсокет, если страница открыта через 127.0.0.1, а не через localhost. В консоли браузера будет Blocked cross-origin request ... allowedDevOrigins, страница не гидратируется и висит на скелетоне. При этом API на том же 127.0.0.1 отвечает нормально, поэтому кажется, что сломан фронт.
Первое, что агент делает в такой ситуации, - перезапускает сервер. Не помогает. Теперь у него в памяти: если страница не грузится, сначала поменять хост на localhost, а уже потом всё остальное.
5. Профили Chrome на HDD
У меня есть браузерная автоматизация, где на каждый прогон создаётся свежий профиль Chrome. Профиль весит около 96 МБ, и в какой-то момент на диске C осталось 48 ГБ. Я перенёс папку с профилями на второй диск через junction, код не трогал. Второй диск оказался HDD.
Под нагрузкой диск упёрся в потолок: disk time 168%, около 200 мс на операцию. Chrome не успевал открыть отладочный порт, и Page.goto падал по таймауту пачками по пять. Со стороны это выглядело как баг в коде или проблемы с сетью, а дело было в диске.
В итоге профили вернулись на SSD, и теперь каждый удаляется сразу после прогона. Две мелочи напоследок:
папку с профилями можно сносить только когда нет живого
chrome.exeс--user-data-dirв ней, иначе Windows держит файлы;если параллельно работают две сессии агента, нельзя убивать Chrome по шаблону имени профиля. Одна моя сессия так закрыла браузеры второй прямо посреди работы. Трогать можно только те PID, которые сессия запустила сама.
Блок для CLAUDE.md
Всё это у меня лежит в памяти агента по отдельным файлам, но если собрать в одно место, получится вот что. Можно скопировать как есть:
## Windows
- PowerShell здесь 5.1: нет && и ||, нет ?: и ??. Цепочки через `A; if ($?) { B }`
- Не вешать 2>&1 на внешние exe, смотреть на $LASTEXITCODE
- Файлы писать инструментом Write, не Set-Content. Если через шелл - только -Encoding utf8
- Процессы искать по CommandLine, не по Name (Python из Store = python3.10.exe)
- "Команда не найдена" -> сначала полный путь, потом вопрос мне
- Dev-серверы открывать через localhost, не 127.0.0.1
- Убивать только свои PID, никогда по шаблону имени
Кто ещё гоняет агентов на Windows без WSL? Интересно, обо что спотыкаетесь вы: подозреваю, что мой список далеко не полный.
KioskNews shows a cleaned-up reading view extracted from the publisher’s page — the original always lives on their site, not ours.