Files
dotfiles/agents/.agents/skills/claude-subagent-playbook/references/codex-exec.md
T
Dmitry DementievandClaude Opus 5 abc00a06be refactor(skills): codex-exec переразобран, имена вердиктов по этапам
- Зачем:
  - рецепт Кодекса читают в момент сборки команды; пересказы, хеджи и
    копии уроков внутри него дают место для импровизации — прогон
    2026-07-30 взял `danger-full-access` «на всякий случай» при верном
    рецепте.
  - вердикты звались по семейству (`review-codex.md`), а Codex ревьюирует
    и постановку, и код в одном `$D` — упавший этап 4 оставлял файл этапа
    2, и триаж молча чинил по чужому вердикту.
- Что:
  - codex-exec.md: удалены копия урока про эффорт (экономика xray, казус
    `.dgst`), причинная механика bwrap, повтор про companion и битая
    ссылка «в уроках ниже»; три пересказа эскалации сведены в один;
    ветка ревьюера стала полноценным рецептом; правила одной ветки
    переехали в свои ветки.
  - codex-exec.md: сторож зависшего треда — признак жизни это рост
    stdout.jsonl, а не статус (урок 2026-07-07 в рецепт не доезжал).
  - prompt-templates.md: имена вердиктов и параметров несут этап —
    `review-<task|code>-<codex|claude>.md`, `review-<этап>-params.md`.
  - model-matrix.md: критерий «цена тихой ошибки этапа» стал единым
    источником, claude-agents.md и codex-exec.md на него ссылаются.
  - lessons-archive.md: датированное уточнение — ключи `resume` сверены
    на 0.145.0, `-m`/`-o`/`--json` доступны вопреки записи 2026-07-21.
- Проверка:
  - сверка 51 клаузы по инвентарю (scratchpad), 24 ключевые — grep'ом;
    все markdown-ссылки скилла резолвятся.
  - сторож прогнан на симуляции трёх исходов: штатное завершение,
    зависание на середине, смерть без отчёта.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 23:15:31 +03:00

18 KiB
Raw Blame History

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 <job-id>.

Далее $D = подпапка задачи в обменном каталоге прогона (определение и конвенция имён — prompt-templates.md).

Сквозные правила (любая ветка)

  • Промпт — файлом, шум — в файлы. Промпт подавай через stdin (-): вход этого вызова занят промптом и должен оставаться открытым — правило < /dev/null для фоновых команд сюда не относится (закроешь — получишь No prompt provided via stdin). stdout/stderr — редиректами в $SCRATCH. В контекст оркестратора читается ровно один файл — финальный отчёт из -o. Промпт собирай конкатенацией, а не переписывай: скелеты — в 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), не по сложности: medium — дефолт для обычных задач (типовая реализация, механика, узкие починки по готовому диагнозу); high — ревью кода, диагноз, нетривиальная или рискованная реализация; xhigh не дефолтить: расход удваивается, а выигрыш мал — только по явному запросу пользователя. Реализация по отревьюированному самодостаточному тикету идёт на medium и при нетривиальной задаче — просадку ловит конвейер (решение пользователя 2026-07-21); но экономию считай по конвейеру целиком, а не по этапу: триаж лишних находок съедает её без остатка. Оверрайды проверены под --strict-config.

  • Запуск — собственным фоновым Bash оркестратора (run_in_background): фоновый процесс детачится, живёт сквозь ходы и будит оркестратора по выходу. Потолок 10 минут — только у фронтграундного Bash; фоновому таймаут не нужен, timeout NNN вокруг codex не ставь. & внутрь команды не добавляй — фон даёт харнесс; с & уведомление придёт от обёртки при живом codex (2026-07-12). Осиротевший процесс подбирает ожидатель: while kill -0 <pid>; do sleep 10; done под run_in_background будит по его реальному выходу.

  • О зависании фоновый запуск молчит: он будит по выходу, а повисший тред не выходит — живой процесс и работающий процесс снаружи неразличимы. Признак жизни — рост stdout.jsonl, а не статус (2026-07-07). Долгий прогон страхуй сторожем — вторым фоновым Bash, который выходит либо когда появился -o-отчёт, либо когда лог перестал расти. Сторож ходит по артефактам, а не по pid: фоновый Bash отдаёт id задачи харнесса, а pid процесса тебе неоткуда взять.

    prev=-1
    while [ ! -s "$D/task-result.md" ]; do
      sleep 300
      [ -s "$D/task-result.md" ] && break
      cur=$(wc -c < "$D/task-stdout.jsonl" 2>/dev/null || echo 0)
      [ "$cur" = "$prev" ] && { echo STALLED; break; }
      prev=$cur
    done
    

    Две ловушки, обе проверены на симуляции 2026-08-03. Выход проверяй и после sleep — иначе сторож объявит застой ровно в тот тик, когда Кодекс дописывал отчёт. И не переводи признак жизни на относительные метки времени: find -newermt '-10 minutes' под bfs (подмена find в части дистрибутивов) молча отдаёт пустой stdout — сторож закричит на первом же тике.

    STALLED — повод посмотреть, а не вердикт: длинная команда внутри песочницы (сборка, тесты) событий не пишет. Разбудил — прочти последнюю строку JSONL и решай сам.

  • thread_id — в первой строке stdout-JSONL ({"type":"thread.started",...}). Забирай сразу после старта: это страховка — если процесс умер или его прибил хост, тред жив: codex exec resume <id> «дай финальный отчёт».

  • resume — только по явному id. Безадресный resume цепляет последний тред сессии — при параллельных тредах подхватит чужой (например, ревью-тред вместо треда реализации).

  • Кросс-платформенно. Рецепты — POSIX под Git Bash (2026-07-09: codex, jq, редиректы под Windows работают). $SCRATCH задавай forward-slash-путём (/c/Users/...), не C:\...; пути в аргументах кавычь; в выводах под Windows возможны \r.

  • Linux: sandbox может молча сломаться обновлением Codex. Сигнатура: web_search работает, а pwd внутри песочницы — нет (bwrap: loopback: Failed RTM_NEWADDR). Обход без sudo use_legacy_landlock=true чинит только read-only, write-режим с ним паникует; полный фикс — AppArmor-профиль, и он под sudo: сам не применяй, дай пользователю готовые команды из codex-sandbox-apparmor.md (!-префикс в чат), после применения профиля напомни убрать use_legacy_landlock. Перед первым прогоном конвейера на новой Linux-машине прогони оттуда же диагностику превентивно.

  • Блок классификатора — сначала сузь песочницу, потом зови пользователя. Первая проверка: нет ли в команде -s danger-full-access — классификатор его блокирует, и он не нужен (почему — в ветке исполнителя). Если блок не из-за песочницы — дай пользователю готовую команду с префиксом ! в чат или попроси сменить режим разрешений. Сработавший путь — прямой exec; companion с --write блокируется и напрямую, и через раннера.

Ветка «исполнитель» (пишет код)

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).
  • Промпт: путь к файлу задачи; мандат — в этапе 3 SKILL.md.
  • До старта сними baseline git status --porcelain; после — сверь отчёт Кодекса с деревом: правок, которых он не называл, быть не должно.

Ветка «ревьюер» (свежий тред, мандат промптом)

# линия кода; для постановки — тот же вызов, но
# review-prompt-task.md -> review-task-codex.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-code-codex.md" - \
  > "$D/review-stdout.jsonl" 2> "$D/review-stderr.txt"
  • Свежим тредом, не тем, что писал код. Вызов тот же, что у исполнителя: запись + авто-аппрув.
  • Эффорт high — дефолт ревью; ниже опускай только по явной просьбе пользователя.
  • Read-only-песочницей ревьюера не режь: она отнимает у него руки — тесты, проверку гипотез, поиск, документацию, первоисточники. Защита — мандат в промпте, готовым файлом prompts/review-boilerplate.md (сборка — prompt-templates.md); Codex держит его надёжно (практика пользователя, 2026-07-10). Дешёвая подстраховка: git status --porcelain до ревью и сверка после — дерево должно остаться нетронутым.
  • Вердикт без находок — проверь лог: работа или отписка. Дёшево: grep -c '"command_execution"' "$D/review-stdout.jsonl" (счётчик выполненных команд) или промежуточные сообщения — jq -r 'select(.item.type=="agent_message") | .item.text' "$D/review-stdout.jsonl". Одна строка APPROVED при пустом счётчике — отписка, перезапускай.
  • -o-файл вердикта — и есть обменный артефакт: на триаже он уходит в fix-промпт конкатенацией, читай его один раз для синтеза и не пересказывай.

Ветка «триаж/починка» (тем же тредом, что писал код)

# resume продолжает тред: среда (-s, -C) задана при создании, не повторяй.
cat "$D/fix-prompt.md" | codex exec resume --json --strict-config <thread-id> \
  -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.
  • Смена эффорта внутри треда кэш не сбрасывает (доля кэша держалась ~93–94% при high→medium, прогоны 06/03/04) — дорогое ревью на high и дешёвая перепроверка на medium в одном треде работают как задумано.
  • Промпт — индекс находок дословно + «закрой перечисленное, больше ничего не меняй; отклонение — только аргументированно».

Раннер-субагент: когда результат надо переварить

Для запуска раннер не нужен (см. сквозные правила) — он нужен, когда сырой результат шумный и его стоит провалидировать или сжать до индекса, прежде чем показывать оркестратору. Тогда раннеру (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) — живучесть даёт именно фоновый запуск.