- Зачем: - боевой прогон 2026-07-30 (тикет #12 v2) закрыл открытый вопрос: расширенный доступ Codex не нужен, штатной связки достаточно. - Что: - codex-exec.md: пункт про блок классификатора начинается с проверки песочницы; в рецепт исполнителя добавлено «шире не бери»; снят кавеат «боем не обкатано» — сеть и docker-сокет работают при штатной связке on-request + auto_review. - lessons.md: датированный урок 2026-07-30 с дополнением по итогам прогона. - Проверка: - чтение обновлённых разделов; факты — из JSONL-логов прогона тикета #12 (docker manifest inspect и make up завершились completed под workspace-write). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
177 lines
16 KiB
Markdown
177 lines
16 KiB
Markdown
# Codex через прямой `codex exec`: рецепты и ловушки
|
||
|
||
Проверено 2026-07-09 (codex-cli 0.142.5, Windows + Git Bash) и 2026-07-12
|
||
(0.144.1, WSL2 Ubuntu: exec/resume/`-o`/фоновый Bash — весь конвейер);
|
||
линукс-эмпирика 07-06/07-07 шла через companion-обёртку и отражена в уроках
|
||
ниже.
|
||
Прямой CLI надёжнее обёртки: её фоновая машина глючит (молчаливая подмена
|
||
анализа Кодекса своим — issue #158, ломает слепое ревью; зависшие статусы),
|
||
и её `--write`-запуски режет классификатор авторежима. Далее `$D` =
|
||
подпапка задачи в обменном каталоге прогона (определение и конвенция —
|
||
[prompt-templates.md](prompt-templates.md)).
|
||
|
||
## Сквозные правила (любая ветка)
|
||
|
||
- **Промпт — файлом, шум — в файлы.** Промпт подавай через stdin (`-`);
|
||
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), она хороша и для анализа/ревью, не только для
|
||
кода. Эффорт по сложности (решение пользователя 2026-07-19): `medium` —
|
||
дефолт для обычных задач (типовая реализация, механика, узкие починки по
|
||
готовому диагнозу); `high` — для более сложных: нетривиальная или
|
||
рискованная реализация, ревью кода, диагноз; `xhigh` — не дефолтить:
|
||
расход удваивается, а выигрыш мал — только по явному запросу пользователя.
|
||
Оверрайды проверены под `--strict-config`.
|
||
Уточнение по эксперименту xray-proxy 03/04 (решение пользователя
|
||
2026-07-21): реализация по отревьюированному самодостаточному тикету на
|
||
`medium` допустима и для нетривиальных задач — конвейер (саморевью + два
|
||
слепых ревью + триаж) ловит просадку (03: 4 MAJOR-гонки найдены и
|
||
починены; 04: 1 «чистый» MAJOR — на уровне high-baseline 06). Но экономию
|
||
считай по всему конвейеру, не по этапу: в 04 medium вышел дороже
|
||
high-baseline (~822k+135k против ~735k+97k свежих) — сэкономленное на
|
||
реализации съел триаж находок. Критерий выбора — цена *тихой* ошибки
|
||
этапа: ревью/постановка/диагноз остаются high (там модель — последняя
|
||
линия обороны). И предел: high на ревью постановки не страхует от
|
||
уверенной выдумки внешних фактов (04: несуществующий однострочный формат
|
||
`.dgst`) — форматы артефактов и версии проверяй реальным запросом или
|
||
фикстурой из первоисточника. Попутная эмпирика: смена
|
||
`model_reasoning_effort` при `resume` кэш треда НЕ сбрасывает (доля кэша
|
||
держалась ~93–94% при high→medium в 06/03/04) — дорогое ревью на high и
|
||
дешёвая перепроверка на medium в одном треде работают как задумано.
|
||
- **Запуск — собственным фоновым Bash оркестратора** (`run_in_background`):
|
||
фоновый процесс детачится, живёт сквозь ходы и будит оркестратора по
|
||
выходу. Потолок 10 минут — только у фронтграундного Bash; фоновому таймаут
|
||
не нужен, `timeout NNN` вокруг codex не ставь. **`&` внутрь команды не
|
||
добавляй** — фон даёт харнесс; с `&` уведомление придёт от обёртки при
|
||
живом codex (эмпирика 2026-07-12). Спасение осиротевшего процесса:
|
||
ожидатель `while kill -0 <pid>; do sleep 10; done` под
|
||
`run_in_background` будит по его реальному выходу.
|
||
- **thread_id — в первой строке stdout-JSONL** (`{"type":"thread.started",...}`).
|
||
Забирай сразу после старта: это страховка — если процесс умер или его
|
||
прибил хост, тред жив: `codex exec resume <id> «дай финальный отчёт»`.
|
||
- **resume — только по явному id.** Безадресный resume цепляет последний тред
|
||
сессии — при параллельных тредах подхватит чужой (например, ревью-тред
|
||
вместо треда реализации).
|
||
- **Вердикт без находок — проверь лог: работа или отписка.** Дёшево:
|
||
`grep -c '"command_execution"' stdout.jsonl` (счётчик выполненных команд)
|
||
или промежуточные сообщения —
|
||
`jq -r 'select(.item.type=="agent_message") | .item.text' stdout.jsonl`.
|
||
Одна строка APPROVED при пустом счётчике — отписка, перезапускай.
|
||
- **Кросс-платформенно.** Рецепты — POSIX под Git Bash (эмпирика 2026-07-09:
|
||
`codex`, `jq`, редиректы под Windows работают). `$SCRATCH` задавай
|
||
forward-slash-путём (`/c/Users/...`), не `C:\...`; пути в аргументах
|
||
кавычь; в выводах под Windows возможны `\r`.
|
||
- **Linux: sandbox может молча сломаться обновлением Codex.** CLI 0.144.1
|
||
сменил backend на bwrap+userns; Ubuntu 24.04 с
|
||
`apparmor_restrict_unprivileged_userns=1` глушит любые команды до запуска
|
||
(`bwrap: loopback: Failed RTM_NEWADDR`). Сигнатура: web_search работает,
|
||
а `pwd` в sandbox — нет. Обход без sudo `use_legacy_landlock=true` чинит
|
||
только read-only (write-режим с ним паникует); полный фикс — AppArmor-
|
||
профиль. Диагностика и рецепты —
|
||
[codex-sandbox-apparmor.md](codex-sandbox-apparmor.md).
|
||
Фикс целиком под sudo — сам не применяй: дай пользователю готовые команды
|
||
из файла рецептов (`!`-префикс в чат), после применения профиля напомни
|
||
убрать `use_legacy_landlock`. Перед первым прогоном конвейера на новой
|
||
Linux-машине прогони диагностику из файла рецептов превентивно.
|
||
- **Блок классификатора — сначала сузь песочницу, потом зови
|
||
пользователя.** Первая проверка: нет ли в команде `-s
|
||
danger-full-access` — классификатор его блокирует, и он не нужен:
|
||
docker и сеть проходят эскалацией под штатной связкой из рецепта ниже
|
||
(боевой прогон 2026-07-30, тикет #12 v2). Если блок не из-за
|
||
песочницы — дай пользователю готовую команду с префиксом `!` в чат или
|
||
попроси сменить режим разрешений. Сработавший путь — прямой 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`-ключе упадёт громко, а не
|
||
проглотится молча (важно для `approvals_reviewer` — в `--help` он не
|
||
документирован, но strict-config его принимает; связка с
|
||
`approval_policy="on-request"` отработала боевой прогон 2026-07-09).
|
||
- Право записи + авто-ревьюер аппрувов. Скоуп ограничивай промптом («коммит
|
||
не делай», границы из файла задачи), не песочницей — жёсткий read-only
|
||
лишает исполнителя тестов и поиска. Шире тоже не бери: `-s
|
||
danger-full-access` блокируется классификатором авторежима и не
|
||
нужен — docker-сокет (`make up`) и сеть (`docker manifest inspect`)
|
||
прошли эскалацией в боевом прогоне 2026-07-30 (тикет #12 v2).
|
||
- Промпт: путь к файлу задачи; мандат — в этапе 3 SKILL.md.
|
||
- До старта сними baseline `git status --porcelain`; после — сверь отчёт
|
||
Кодекса с деревом: правок, которых он не называл, быть не должно.
|
||
|
||
## Ветка «ревьюер» (свежий тред, мандат промптом)
|
||
|
||
Тот же вызов, что у исполнителя — запись + авто-аппрув, — но **свежим
|
||
тредом**, не тем, что писал код. Read-only-песочницей ревьюера не режь: она
|
||
отнимает у него руки — тесты, проверку гипотез, поиск, документацию,
|
||
первоисточники. Защита — мандат в промпте, готовым файлом
|
||
[prompts/review-boilerplate.md](prompts/review-boilerplate.md) (сборка —
|
||
[prompt-templates.md](prompt-templates.md)); Codex держит его надёжно
|
||
(практика пользователя, 2026-07-10). Дешёвая подстраховка:
|
||
`git status --porcelain` до ревью и сверка после — дерево должно остаться
|
||
нетронутым. Кавеат снят 2026-07-30: сеть под workspace-write по
|
||
умолчанию закрыта, но эскалация через авто-аппрув её проходит — в
|
||
боевом прогоне тикета #12 (v2) у исполнителя прошли и сеть
|
||
(`docker manifest inspect`), и docker-сокет (`make up`, `docker compose
|
||
down`). Отдельных событий эскалации в `--json`-логе при этом нет —
|
||
успешные команды выглядят обычными `completed` (история вопроса —
|
||
[../handoffs/20260710-codex-revyuer-write-i-sliyanie-handoffov.md](../handoffs/20260710-codex-revyuer-write-i-sliyanie-handoffov.md)).
|
||
|
||
`-o`-файл вердикта — и есть обменный артефакт: на триаже он уходит в
|
||
fix-промпт конкатенацией, читай его один раз для синтеза и не пересказывай.
|
||
|
||
## Ветка «триаж/починка» (тем же тредом, что писал код)
|
||
|
||
```bash
|
||
cat "$D/fix-prompt.md" | codex exec resume --json <thread-id> \
|
||
-o "$D/fix-result.md" - \
|
||
> "$D/fix-stdout.jsonl" 2> "$D/fix-stderr.txt"
|
||
```
|
||
|
||
- `resume` не принимает `-s`/`-C` — песочница и CWD наследуются от исходного
|
||
треда (write-тред остаётся write); модель наследуется, но переопределяется
|
||
`-m` при нужде.
|
||
- Промпт — индекс находок **дословно** + «закрой перечисленное, больше
|
||
ничего не меняй; отклонение — только аргументированно».
|
||
|
||
## Раннер-субагент: когда результат надо переварить
|
||
|
||
Для запуска раннер не нужен (см. сквозные правила) — он нужен, когда сырой
|
||
результат шумный и его стоит провалидировать или сжать до индекса, прежде
|
||
чем показывать оркестратору. Тогда раннеру (`fast-worker`; нет в
|
||
репозитории — `general-purpose`) отдай
|
||
**готовую команду** и правила разбора — не постановку задачи: тонкий агент,
|
||
получив постановку, реализует её сам (казус форвардера, lessons 2026-07-06).
|
||
Свой 10-минутный потолок на синхронный Bash раннер обходит отслеживаемым
|
||
ожиданием: команда запускается его собственным фоновым Bash, а ждёт он
|
||
процессом-ожидателем (`until grep -q '<маркер>' лог; do sleep 30; done` под
|
||
`run_in_background`) — харнесс переинвоцирует его по выходу процесса.
|
||
«Жду уведомления» без отслеживаемого процесса не будит никогда, а голый
|
||
поллер вне `run_in_background` умирает при смене хода (эмпирика
|
||
2026-07-07) — живучесть даёт именно фоновый запуск.
|
||
|
||
Companion-плагин (`codex-companion.mjs` в кэше плагинов) — только для
|
||
ручного интерактива (`status`, `result <job-id>`); конвейер ходит прямым
|
||
exec.
|