# Codex через прямой `codex exec`: рецепты и ловушки Проверено 2026-07-09 (0.142.5, Windows + Git Bash) и 2026-07-12 (0.144.1, WSL2 Ubuntu: exec/resume/`-o`/фоновый Bash — весь конвейер); ключи `exec` и `resume` сверены 2026-08-03 на 0.145.0. Конвейер ходит **прямым exec**, не плагином-обёрткой: у обёртки глючит фоновая машина — молчаливая подмена анализа Кодекса своим (issue #158, ломает слепое ревью) и зависшие статусы, — а её `--write`-запуски режет классификатор авторежима. Сама обёртка (`codex-companion.mjs` в кэше плагинов) остаётся для ручного интерактива: `status`, `result `. Далее `$D` = подпапка задачи в обменном каталоге прогона (определение и конвенция имён — [prompt-templates.md](prompt-templates.md)). ## Сквозные правила (любая ветка) - **Предполёт — на новой машине, в новом харнессе и после обновления или переустановки Codex.** Две проверки. На Linux/WSL песочница: оба режима и границы записи по [codex-sandbox-apparmor.md](codex-sandbox-apparmor.md). Вывод доходит до файла: `codex --version > f; wc -c f` — непусто значит, что рецепты ниже работают как написаны. Пусто: среда теряет унаследованный файловый дескриптор, и на входе, и на выходе (известный случай — T3 Code, [lessons.md](lessons.md) 2026-08-06); тогда каждый поток идёт своим каналом в свой файл, а код возврата держит `pipefail`: ```bash set -o pipefail cat "$D/task-prompt.md" | codex exec --json … -o "$D/task-result.md" - \ 2> >(cat > "$D/task-stderr.txt") | cat > "$D/task-stdout.jsonl" ``` Без `pipefail` статус вернёт `cat`, и отказ Кодекса прочтётся успехом. Подстановка процесса дописывает stderr асинхронно: читаешь этот файл тем же вызовом — поставь `wait`. - **Промпт — файлом через канал, шум — в файлы.** Промпт подавай каналом: `cat "$D/prompt.md" | codex exec … -`. Канал читается в любой среде, а редирект из файла (`- < prompt.md`) в среде из предполёта отвечает `No prompt provided via stdin`. Вход этого вызова занят промптом и должен оставаться открытым — правило `< /dev/null` для фоновых команд сюда не относится (закроешь — получишь то же сообщение). stdout/stderr — редиректами в `$SCRATCH`, а если предполёт показал пусто — каналами оттуда же. В контекст оркестратора читается ровно один файл — финальный отчёт из `-o`. Промпт **собирай конкатенацией, а не переписывай**: скелеты — в [prompt-templates.md](prompt-templates.md), Write'ом пиши только параметрический блок задачи; чужой контент (вердикты ревью, отчёты) в промпт попадает через `cat a.md b.md > prompt.md` — дословно и без повторного прохода через твой контекст (2026-07-10: переписывание гоняло находки через оркестратора до шести раз). - **Модель и глубину рассуждения задавай явно** (`-m`, `-c 'model_reasoning_effort="..."'`): дефолты берутся из `~/.codex/config.toml` и плавают за последней интерактивной сессией пользователя — из конфига не наследовать, при расхождении с правилом — спроси пользователя. Боевая модель — **`gpt-5.6-sol`** (решение пользователя 2026-07-12), она хороша и для анализа/ревью, не только для кода. Ступень — по цене *тихой* ошибки этапа ([model-matrix.md](model-matrix.md)), не по сложности: `medium` — дефолт для обычных задач (типовая реализация, механика, узкие починки по готовому диагнозу); `high` — ревью кода, диагноз, нетривиальная или рискованная реализация; `xhigh` не дефолтить: расход удваивается, а выигрыш мал — только по явному запросу пользователя. Исключение из строки про `high`: реализация по отревьюированному самодостаточному тикету **допустима** на `medium` и при нетривиальной задаче — просадку ловит конвейер (решение пользователя 2026-07-21); но экономию считай по конвейеру целиком, а не по этапу: триаж лишних находок съедает её без остатка. Оверрайды проверены под `--strict-config`. - **Запуск — собственным фоновым Bash оркестратора** (`run_in_background`): фоновый процесс детачится, живёт сквозь ходы и будит оркестратора по выходу. Потолок 10 минут — только у фронтграундного Bash; фоновому таймаут не нужен, `timeout NNN` вокруг codex не ставь. **`&` внутрь команды не добавляй** — фон даёт харнесс; с `&` уведомление придёт от обёртки при живом codex (2026-07-12). Осиротевший процесс подбирает ожидатель: `while kill -0 ; do sleep 10; done` под `run_in_background` будит по его реальному выходу. - **О зависании фоновый запуск молчит**: он будит по выходу, а повисший тред не выходит — живой процесс и работающий процесс снаружи неразличимы. Признак жизни — рост JSONL-лога, а не статус (2026-07-07). Долгий прогон страхуй сторожем — вторым фоновым Bash, который выходит либо когда появился `-o`-отчёт, либо когда лог перестал расти. Сторож ходит по артефактам, а не по pid: фоновый Bash отдаёт id задачи харнесса, а pid процесса тебе неоткуда взять. Пара файлов — **от той ветки, которую страхуешь** (у ревью `review-impl-*`, у починки `fix-*`): подставь их в первую строку, иначе сторож проверит чужой отчёт, найдёт его на месте и выйдет, не сделав ни одного тика. ```bash REPORT="$D/task-result.md"; LOG="$D/task-stdout.jsonl" # ветка исполнителя prev=-1 while [ ! -s "$REPORT" ]; do sleep 300 [ -s "$REPORT" ] && break [ -e "$LOG" ] || { echo NOLOG; break; } cur=$(wc -c < "$LOG") [ "$cur" = "$prev" ] && { echo STALLED; break; } prev=$cur done ``` Проверено симуляцией 2026-08-03: выход проверяй **и после `sleep`**, иначе сторож объявит застой в тот самый тик, когда Кодекс дописывал отчёт. И не переводи признак жизни на относительные метки времени: `find -newermt '-10 minutes'` под `bfs` (подмена `find` в части дистрибутивов) ругается в stderr, а в подстановку команды отдаёт пустой stdout — ошибка проглотится, и сторож закричит на первом же тике. `NOLOG` — путь неверен или запуск не состоялся: редирект создаёт лог сразу, так что через тик его не быть не может. `STALLED` — повод посмотреть, а не вердикт: длинная команда внутри песочницы (сборка, тесты) событий не пишет. Разбудил — прочти последнюю строку JSONL и решай; решил, что жив, — перезапусти сторож: выходом из цикла он страховку снял. - **thread_id — в первой строке stdout-JSONL** (`{"type":"thread.started",...}`). Забирай сразу после старта: это страховка — если процесс умер или его прибил хост, тред жив: `codex exec resume «дай финальный отчёт»`. - **resume — только по явному id.** Безадресный resume цепляет последний тред сессии — при параллельных тредах подхватит чужой (например, ревью-тред вместо треда реализации). - **Кросс-платформенно.** Рецепты — POSIX под Git Bash (2026-07-09: `codex`, `jq`, редиректы под Windows работают). `$SCRATCH` задавай forward-slash-путём (`/c/Users/...`), не `C:\...`; пути в аргументах кавычь; в выводах под Windows возможны `\r`. - **Linux: shell-команды падают до запуска, хотя web_search работает.** При ошибке bwrap, в том числе `Failed RTM_NEWADDR`, сначала установи причину и фактический способ запуска по [codex-sandbox-apparmor.md](codex-sandbox-apparmor.md). Там же — штатная настройка, переход со старого обхода и критерий готовности. При провале предполёта сначала восстанови среду, затем запускай рабочие задачи. - **Блок классификатора — сначала сузь песочницу, потом зови пользователя.** Первая проверка: нет ли в команде `-s danger-full-access` — классификатор его блокирует, и он не нужен (почему — в ветке исполнителя). Если блок не из-за песочницы — дай пользователю готовую команду с префиксом `!` в чат или попроси сменить режим разрешений. Сработавший путь — прямой exec; companion с `--write` блокируется и напрямую, и через раннера. ## Ветка «исполнитель» (пишет код) ```bash cat "$D/task-prompt.md" | codex exec --json --strict-config \ -s workspace-write -m gpt-5.6-sol \ -c 'model_reasoning_effort="high"' \ -c 'approval_policy="on-request"' -c 'approvals_reviewer="auto_review"' \ -o "$D/task-result.md" - \ > "$D/task-stdout.jsonl" 2> "$D/task-stderr.txt" ``` - Эффорт в примере — `high` (сложная задача); обычной задаче ставь `medium` по сквозному правилу выше. - **`--strict-config` — не про песочницу, а про опечатку.** С ним нераспознанный `-c`-ключ валит запуск громко; без него он проглатывается молча, и ты едешь на дефолте вместо заданной ступени. Заодно strict-config принимает `approvals_reviewer`, которого нет в `--help`: связка с `approval_policy="on-request"` отработала боевой прогон 2026-07-09. - Право записи + авто-ревьюер аппрувов. Скоуп ограничивай промптом («коммит не делай», границы из файла задачи), не песочницей — жёсткий read-only лишает исполнителя тестов и поиска. - **Шире тоже не бери.** `-s danger-full-access` блокируется классификатором авторежима и не нужен: под штатной связкой из рецепта у Codex прошли и сеть (`docker manifest inspect`), и docker-сокет (`make up`, `docker compose down`) — эскалацией через авто-аппрув (боевой прогон 2026-07-30, тикет #12 v2). Отдельных событий эскалации в `--json`-логе при этом нет: успешные команды выглядят обычными `completed` — не ищи их там (история вопроса — [../handoffs/20260710-codex-revyuer-write-i-sliyanie-handoffov.md](../handoffs/20260710-codex-revyuer-write-i-sliyanie-handoffov.md)). - Промпт: путь к файлу задачи; мандат — в этапе 3 SKILL.md. - До старта сними baseline `git status --porcelain`; после — сверь отчёт Кодекса с деревом: правок, которых он не называл, быть не должно. ## Ветка «ревьюер» (свежий тред, мандат промптом) ```bash # линия дефектов этапа 4; линия уместности и этап 2 — тот же вызов, # другой промпт и другой вердикт (имена — prompt-templates.md) cat "$D/review-prompt-code.md" | codex exec --json --strict-config \ -s workspace-write -m gpt-5.6-sol \ -c 'model_reasoning_effort="high"' \ -c 'approval_policy="on-request"' -c 'approvals_reviewer="auto_review"' \ -o "$D/review-impl-codex.md" - \ > "$D/review-impl-stdout.jsonl" 2> "$D/review-impl-stderr.txt" ``` - **Ветка та же для любого слепого ревью** — кода, постановки, документа, самого скилла: рецепт копируй целиком, линзу задавай файлом линии, а не пересборкой команды. - **Свежим тредом**, не тем, что писал код. Вызов тот же, что у исполнителя: запись + авто-аппрув. - Эффорт `high` — дефолт ревью; ниже опускай только по явной просьбе пользователя. - **Песочница у ревьюера та же, что у исполнителя — `workspace-write`.** Линии различает свежесть треда и мандат, а не привилегия: read-only отнял бы руки — тесты, проверку гипотез, поиск, документацию, первоисточники, — и ревью документа не исключение, там руками проверяют команды и рецепты. Защита — мандат в промпте, готовым файлом [prompts/review-boilerplate.md](prompts/review-boilerplate.md) (сборка — [prompt-templates.md](prompt-templates.md)); Codex держит его надёжно (практика пользователя, 2026-07-10). Дешёвая подстраховка: `git status --porcelain` до ревью и сверка после — дерево должно остаться нетронутым. - **Вердикт без находок — проверь лог: работа или отписка.** Дёшево: `grep -c '"command_execution"' "$D/review-impl-stdout.jsonl"` (счётчик выполненных команд) или промежуточные сообщения — `jq -r 'select(.item.type=="agent_message") | .item.text' "$D/review-impl-stdout.jsonl"`. Одна строка APPROVED при пустом счётчике — отписка, перезапускай. - `-o`-файл вердикта — и есть обменный артефакт: на триаже он уходит в fix-промпт конкатенацией, читай его один раз для синтеза и не пересказывай. ## Ветка «триаж/починка» (тем же тредом, что писал код) ```bash # resume продолжает тред: среда (-s, -C) задана при создании, не повторяй. cat "$D/fix-prompt.md" | codex exec resume --json --strict-config \ -o "$D/fix-result.md" - \ > "$D/fix-stdout.jsonl" 2> "$D/fix-stderr.txt" ``` - **`resume` продолжает тред, а не заводит новый — среда принадлежит треду, не ходу.** Песочница и CWD заданы при создании и на продолжении не переуказываются: ключей `-s` и `-C` у `resume` просто нет, дописанные по образцу `exec` они дают `exit 2, unexpected argument`. Write-тред остаётся write. Ручки хода остаются: `-m` (модель), `-c model_reasoning_effort=…`, `-o`, `--json`, `--strict-config`. `-c sandbox_mode=…` разбирается без ошибки, но среду продолжения не меняет — не рассчитывай на него. Ключи сверены 2026-08-03 на codex-cli 0.145.0. Внимание, нужно указать модель и effort, иначе возьмет дефолтные из конфига - а там может оказаться совсем неподходящее. - Менять effort внутри треда нежелательно - может сбросить кэш. - Промпт — индекс находок **дословно** + «закрой перечисленное, больше ничего не меняй; отклонение — только аргументированно». ## Раннер-субагент: когда результат надо переварить Для запуска раннер не нужен (см. сквозные правила) — он нужен, когда сырой результат шумный и его стоит провалидировать или сжать до индекса, прежде чем показывать оркестратору. Тогда раннеру (`fast-worker`; нет в репозитории — `general-purpose`) отдай **готовую команду** и правила разбора, не постановку задачи: тонкий агент, получив постановку, реализует её сам (казус форвардера, lessons-archive 2026-07-06). Свой 10-минутный потолок на синхронный Bash раннер обходит тем же приёмом, что и оркестратор, — отслеживаемым ожиданием: команда запускается его собственным фоновым Bash, а ждёт он процессом-ожидателем (`until grep -q '<маркер>' лог; do sleep 30; done` под `run_in_background`), и харнесс переинвоцирует его по выходу процесса. «Жду уведомления» без отслеживаемого процесса не будит никогда, а голый поллер вне `run_in_background` умирает при смене хода (2026-07-07) — живучесть даёт именно фоновый запуск.