Files
dotfiles/agents/.agents/skills/claude-subagent-playbook/references/codex-exec.md
T
Dmitry DementievandClaude Opus 5 78ee8bfa48 fix(skills): сторож зависшего треда развязан с веткой исполнителя
- Зачем:
  - сторож был зашит на task-result.md и task-stdout.jsonl, то есть на
    артефакты этапа 3. На ревью и на починке отчёт исполнителя уже лежит
    рядом, цикл не делал ни одного тика и выходил молча — страховка
    отсутствовала ровно там, где прогон длиннее всего.
  - отсутствующий лог был неотличим от неростущего: `|| echo 0` выдавал
    ноль два тика подряд, и неверный путь читался как застой.
- Что:
  - пара файлов вынесена в переменные REPORT и LOG первой строкой, с
    указанием подставлять их от страхуемой ветки.
  - отсутствие лога стало отдельным сигналом NOLOG (редирект создаёт лог
    сразу, так что через тик его не быть не может).
  - дописано, что выход из цикла снимает страховку: после STALLED сторож
    перезапускают руками.
  - поправлено описание ловушки bfs: он ругается в stderr, а пустым
    отдаёт только stdout — «молча» было неверно.
- Проверка:
  - симуляция четырёх исходов: штатное завершение, зависание, неверный
    путь к логу (NOLOG) и ветка ревью при лежащем рядом отчёте этапа 3 —
    последняя как раз и не работала до правки.

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

223 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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](prompt-templates.md)).
## Сквозные правила (любая ветка)
- **Промпт — файлом, шум — в файлы.** Промпт подавай через stdin (`-`): вход
этого вызова занят промптом и должен оставаться открытым — правило
`< /dev/null` для фоновых команд сюда не относится (закроешь — получишь
`No prompt provided via 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), она хороша и для анализа/ревью, не только для
кода. Ступень — по цене *тихой* ошибки этапа
([model-matrix.md](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` будит по
его реальному выходу.
- **О зависании фоновый запуск молчит**: он будит по выходу, а повисший тред
не выходит — живой процесс и работающий процесс снаружи неразличимы.
Признак жизни — рост 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 <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](codex-sandbox-apparmor.md) (`!`-префикс в чат),
после применения профиля напомни убрать `use_legacy_landlock`. Перед первым
прогоном конвейера на новой Linux-машине прогони оттуда же диагностику
превентивно.
- **Блок классификатора — сначала сузь песочницу, потом зови пользователя.**
Первая проверка: нет ли в команде `-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` — дефолт ревью; ниже опускай только по явной просьбе
пользователя.
- 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 <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) — живучесть даёт
именно фоновый запуск.