Skip to content
Back to skills

Codex Audit

DSecurity

Codex CLI read-only: аудит/ревью, adversarial (сломать решение), advocate (опровергнуть наш вывод), второе мнение, сверка с планом (compliance), диагностика при затыке. "отправь codex на аудит", "состязательное ревью". Пишет только `codex-implement`.

  • 8 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 29, 2026
toolspythonrustshellbashnodeawsgitapisecurityperformance

Works with

  • claude code
  • cli
  • api

Security analysis

D50/100
  • criticalModifies startup scripts or system services for persistence
  • criticalModifies startup scripts or system services for persistence
  • criticalModifies startup scripts or system services for persistence
  • criticalSends environment variables or credentials to an external URL

Pro shows the line behind each finding and how to fix it

Scanned October 3, 2026

npx -y skills add dewil/claude-toolkit --skill codex-audit --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Codex Audit?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Codex Audit
[![Security: D — Skills Directory](https://www.skillsdirectory.com/api/skills/dewil-codex-audit-claude-toolkit/badge)](https://www.skillsdirectory.com/skills/dewil-codex-audit-claude-toolkit)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

SKILL.md
---
name: codex-audit
description: >-
  Codex CLI read-only: аудит/ревью, adversarial (сломать решение), advocate (опровергнуть наш вывод), второе мнение, сверка с планом (compliance), диагностика при затыке. "отправь codex на аудит", "состязательное ревью". Пишет только `codex-implement`.
---

# codex-audit

Скилл для делегирования в `codex` CLI на этой машине задач, требующих **другой модели как независимого второго взгляда**, без права что-либо менять в репо (read-only). Codex изолирован: не видит контекст текущей сессии Claude, но читает файлы репозитория. Ценность не в том, что он "умнее" - а в **разнообразии мышления**: другая модель ловит другие классы ошибок, чем основной агент. Право писать у codex есть только в соседнем скилле `codex-implement`; здесь его нет ни в одном режиме.

## Файловая песочница

Все запуски, включая `resume`, идут через `scripts/codex-sandbox.py`: bwrap оставляет видимыми объект, CODEX_HOME и системные файлы, перекрывает секреты по именам. В аудите объект доступен только на чтение; HOME и /tmp пустые, сеть общая. При аудите основного чекаута видны секреты в истории git и `.git/config` (в том числе токены в URL remote); утекшие секреты требуют ротации. Перекрытие идет только по именам: `credentials.json`, `.aws/`, базы и содержимое `venv`/`node_modules` остаются видимыми. `<toolkit>` в шаблонах - абсолютный путь к claude-toolkit; `-C` подставляет скрипт. Код 2 - сообщить пользователю причину из stderr, при отсутствии bwrap поставить `bubblewrap`; без рабочей песочницы Codex не запускать. Скрипт поддерживает только Linux; на macOS он отказывает.

## Режимы

| Режим | Когда | Effort | Формат ответа Codex | Файлы в `docs/` |
|---|---|---|---|---|
| `audit` (дефолт) | глубокий разбор кода/диффа/плана; "отправь на аудит", "пусть codex проверит" | `medium` | по каждому вопросу вердикт ok / risk / blocker + 1-2 предложения | `audit-plan-<topic>` / `audit-report-<topic>` |
| `adversarial` | состязательное ревью со скептической позицией; "сломай решение", "состязательное ревью", "попробуй сломать через codex" - нужно не подтверждение, а активный поиск дыр | `medium`, на необратимом `high` | по каждому компоненту: чем и при каких условиях сломается; "проблем нет" - только с перечнем проверенных атак | `adversarial-plan-<topic>` / `adversarial-report-<topic>` |
| `second-opinion` | независимый взгляд на подход/решение/дифф; "второе мнение", "посоветуйся"; по умолчанию после крупной работы | `medium` | согласен / не согласен + альтернативы + трейдоффы (не чеклист блокеров) | `consult-plan-<topic>` / `consult-report-<topic>` |
| `compliance` | сверка реализации с исходным планом; "проверь, что реализовал план", "сверь дифф с планом" - что сделано, что пропущено, что добавлено сверх плана | `medium` | таблица "пункт плана -> статус (реализовано / частично / пропущено / отклонено) -> комментарий" + блок "сверх плана" + блок "вне спеки" (находки, не нарушающие критерий приемки или инвариант домена; мерж не блокируют, уезжают в бэклог - `sdd-pipeline.md`, шаг 6) | `compliance-plan-<topic>` / `compliance-report-<topic>` |
| `advocate` | проверить не объект, а **наш вывод о нем**: "справедлив ли разбор", "не придираемся ли", перед тем как отдать оценку наружу | `medium` | по каждому пункту вывода: справедлив / преувеличен / несправедлив / не проверяем + счет по вердиктам | `advocate-plan-<topic>` / `advocate-report-<topic>` |
| `escalation` | основной агент застрял (3+ провалившихся фикса, гипотезы не подтверждаются, субагент BLOCKED) - до похода к человеку | `high` | диагноз + root cause с доказательствами + рекомендованный подход | `escalate-plan-<topic>` / `escalate-report-<topic>` |

Общая обвязка одинакова во всех режимах: запуск через `scripts/codex-sandbox.py audit --root <project-root> -- -s read-only`, модель - из профиля роли `audit` (`$CODEX_HOME/audit.config.toml`, правило 3; дефолт codex не участвует; сверка реализации codex - профиль `compliance`), pre-check и пост-сбор лимитов, блок про субагентов в плане, требование к Codex писать `session id` первой строкой отчета. Различаются только `effort`, фрейминг плана/промпта, формат ответа и имена файлов.

## Когда применять

- Пользователь явно просит передать что-то в codex ("отправь codex", "пусть codex посмотрит", "второе мнение", "посоветуйся с codex").
- Нужна независимая проверка плана/диффа/архитектурного решения второй моделью.
- Нужно **состязательное (adversarial) ревью** (режим `adversarial`): не "подтверди, что ок", а "исходи из того, что дефект есть, и найди, чем решение сломается". Зовется фразами "состязательное ревью", "сломай решение", "попробуй сломать через codex".

  Важно понимать его границу: `adversarial` направлен **на объект** и наши выводы о нем не проверяет. Два прохода с общей установкой "найди дефекты" - не независимая проверка, а удвоение одного уклона: вопрос "а справедлив ли сам вывод" не задает ни один из них, зато их совпадение читается как схождение. Для проверки выводов есть `advocate` (ниже).
- Нужно **проверить собственный разбор** (режим `advocate`): на вход идет не объект, а **наша аналитика по нему** плюс сам объект, установка - "исходи из того, что вывод необоснован". Зовется фразами "проверь мой разбор", "не придираемся ли", "проход адвоката". Прецедент: разбор, где два прохода "подтвердили" друг друга, у адвоката дал 3 справедливых пункта из 16 - и все возражения, которые потом прислал клиент, воспроизвелись независимо, притом что его текст на вход не подавался.
- Нужна **сверка кода с планом** (режим `compliance`): сравнить реализованный дифф с исходным планом - что сделано, что пропущено, что отклонилось, что добавлено сверх плана. Зовется фразами "проверь, что реализовал план", "сверь дифф с планом", "compliance check". Уместно после фазы плана, до закрытия задачи.

  **В кодовом проекте с SDD-конвейером сверка перед мержем обязательна** - критерий и порядок заданы правилом `rules/sdd-pipeline.md` (кодовая специализация; в проекте без него этот абзац не действует), здесь только механика. Довод один: прозу инвариантов тесты не ловят, implementer смотрит на критерии приемки, и только второй взгляд читает прозу. Это изъятие из общего "мелкая задача - не плодим вызовы" в "Когда НЕ применять": мелкость тут не довод. Сверка идет против спеки фичи **и** доменной спеки, включая раздел "Известные дыры": фича, закрывающая дыру, обязана убрать строку из дыр и добавить тест в трассируемость - несделанное это расхождение того же класса, что нереализованный пункт.

  Повторный раунд **по правкам из первого отчета** при исходном `medium` - `resume` той же сессии; после другого effort - новая сессия с исходной спекой и отчетом (тот случай, где он выигрывает: контекст отчета нужен целиком), с запросом "закрыто / осталось / новое" по каждому его пункту. Новый вопрос по той же фиче - новая сессия, по общему правилу выше.

  **Кто писал, тот не сверяет.** Общий контракт - `rules/sdd-pipeline.md`: другая фактическая модель и отдельный контекст, подтвержденные по запуску. В этом адаптере реализацию Codex сверяет профиль `compliance`, отличающийся от `implement`; реализацию другого автора - профиль `audit`, отличающийся от модели автора. Роль `audit` сама по себе независимости не доказывает. Для полномочий, безопасности, потери данных и миграций нужны подтвержденные границы доступа и проверка риск-сценариев. Нет подходящей среды - сохрани блокер, без обязательной привязки к Claude и без фолбэка на автора.

  Codex в `read-only` зависимости не ставит, и статический анализ по диффу - его единственный инструмент. Зависимости должны лежать внутри объекта: внешние симлинки в bwrap недоступны. В план так и писать: "тесты не запускай, суди по коду".
- Основной агент застрял (режим `escalation`): отладка зашла в тупик, делегированный субагент вернул BLOCKED, несколько фиксов подряд не сработали - зовем Codex до того, как идти к пользователю.
- **Second-opinion по умолчанию.** После крупного изменения или завершенной фазы плана уместно прогнать `second-opinion`, не дожидаясь явной просьбы - это часть нормального workflow. Но с оглядкой: сначала pre-check лимита, и не на каждую мелочь (см. "Когда НЕ применять"). Forcing-функции через hook здесь намеренно нет - дисциплину держит этот скилл, а не блокировка.

## Когда НЕ применять

- Любые изменения файлов/коммиты/деплой - codex в этом скилле всегда read-only. Фиксы по находкам применяет сам Claude в своей сессии, не Codex. Реализация по спеке с правом записи - отдельный скилл `codex-implement`, со своим разрешением в settings и своим worktree; сюда она не переносится.
- Опечатки, пропущенные импорты, очевидные ошибки - не повод звать вторую модель.
- До того как сам провел первичное расследование - сначала разберись, потом эскалируй.
- Если задача мелкая и решается тут же без второго мнения - не плодим вызовы.

## Как встроить валидацию в поток, чтобы ее не выключили

Второе мнение ловит то, чего свой взгляд не ловит по построению, но платится временем: прогон на `high` думает минутами. Если каждое решение превращается в паузу, валидацию тихо перестают звать - отказываются не от качества, а от простоя. Правила ниже про то, **когда звать, что подавать, на какой глубине и где ставить гейт**; механика (режимы, `resume`, фон, pre-check) - в остальных разделах этого файла.

**Валидируется решение, а не реализация.** На вход - записка на 10-15 строк: что меняем, почему именно так, какая альтернатива отвергнута и чем. Не дифф и не список файлов. Разбор диффа требует перечитать код - это десятки минут на `high`; разбор решения идет на `medium` за минуты и ловит ровно то, ради чего валидацию заводят, - неверную развилку, причем до того, как написана первая строка. Дифф отдается отдельно и только там, где цена ошибки в реализации выше цены ошибки в замысле: миграция боевых данных, проверка полномочий, тонкое место с гонкой.

**Продолжение сессии экономит чтение, но фиксирует уровень.** `resume` наследует модель и effort исходной сессии и переданные игнорирует. Отсюда ловушка, которая не дает ошибки и потому не замечается: серия коротких консультаций, запущенных продолжением глубокого аудита, целиком идет на `high`, хотя по смыслу каждая из них - обычная развилка. Прецедент 01.09.2026: аудит был заведен на `high` заслуженно (17 находок), две следующие валидации решений пошли продолжением и унаследовали его; заметили не по ошибке, а по расходу квоты.

Обмен делается осознанно, и по умолчанию он такой:

- **валидация решения - новой сессией на `medium`.** Записка на 10-15 строк самодостаточна, перечитывать репозиторий модели незачем, а дорог именно уровень, а не повторное чтение;
- **Вне SDD-compliance `resume` - когда вопрос вправду опирается на прежний глубокий разбор** (re-review фиксов, уточнение находки). Тогда унаследованный `high` - осознанная плата, а не побочный эффект.

Обратное соотношение (продолжение выгоднее) бывает на щедром тарифе, где входные токены дороже уровня, - но это надо знать про свою подписку, а не предполагать.

**Уровень называется вслух до запуска, вместе с причиной** - и с тем, шагнул ли ты от дефолта таблицы и почему. Расход виден только постфактум, в статистике лимитов, а унаследованный уровень в отчете выглядит ровно так же, как выбранный (`rules/silent-failure.md`).

**Запуск фоновый.** Ожидание перекрывается работой, а не встает в нее паузой.

**Гейт на мерж, а не на старт.** Реализация начинается сразу, вердикт учитывается перед вливанием в основную ветку. Обратный порядок ("сперва одобрение, потом код") дает тот же результат по качеству и вдвое больший срок: ожидание встает в критический путь, хотя ждать в нем нечего. Риск переделки невелик - записку на 15 строк переписать дешево, а вердикт приходит раньше, чем ветка готова.

**Глубина по необратимости.** Дефолт везде `medium`; `high` берется только там, где ошибка не откатывается, - миграции боевых данных, границы доступа и полномочий, схема данных, механика доставки или удаления файлов. Обычная развилка проектирования на `high` не нужна; шаг вниз до `low` на мелком механическом объекте - вилка в правиле 3. Иначе упираешься в квоту, и на необратимое ее не остается: после одного глубокого аудита в пятичасовом окне оставалось 43 процента.

**Граница приема.** Он про решения, у которых есть развилка: два допустимых способа, и выбор между ними меняет результат. То, что проверяется фактом - упал тест, вернулся код ответа, сработала команда, - валидировать тратно: там дешевле проверить, чем спросить. Записка "правильно ли я понимаю, что запрос вернет 404" стоит минут ожидания там, где сам запрос занимает секунду.

Ничего из этого не отменяет pre-check лимита перед каждым запуском (правило 8), верификацию находок своими руками и запрет делегировать проверку собственной работы (`rules/subagents-usage.md`): ценность здесь именно в другой модели, а не во втором проходе тем же взглядом.

## Жесткие правила

1. **Всегда** запускать с `-s read-only`. Других режимов в этом скилле нет.
2. **Всегда** фиксировать рабочий каталог через `-C <project-root>`. В `-C` подставляй корень текущего проекта (абсолютный путь рабочего каталога), не хардкодь путь.
3. **Всегда** явно фиксировать модель и effort в команде:
   - **Модель - из профиля роли, дефолт codex не участвует.** Роли три, и профили-файлы у них по фиксированным именам: `$CODEX_HOME/audit.config.toml` - все режимы этого скилла (`audit`, `adversarial`, `advocate`, `second-opinion`, `escalation` и `compliance` реализаций Claude), `$CODEX_HOME/compliance.config.toml` - режим `compliance`, когда сверяется реализация codex (после `codex-implement`), `$CODEX_HOME/implement.config.toml` - реализация (`codex-implement`). В файле одна строка `model = "<slug>"`; в команду попадает `-p <роль> -c model="<slug>"` (сниппет `MODEL_FLAG` в "Шаблоне вызова": `-p` накладывает профиль целиком, явный `-c model` фиксирует имя в логе и саммари). Дефолт `config.toml` скилл **не читает и на него не падает**: этот файл переписывает интерактивный выбор модели в TUI codex, и прецедент 05.09.2026 ровно такой - после выбора в меню реализации и сверки пошли одной моделью, потому что дефолт незаметно совпал с профилем. Профиля нет или в нем нет читаемого `model` - **стоп** и просьба к пользователю завести файл одной строкой, а не тихий запуск тем, что стоит в консоли. Имена моделей в скилле не хардкодятся: они живут в профилях на машине, выбор - решение владельца аккаунта по цене и замеру ("Как сравнить модели"), и codex мигрирует устаревший slug сам. Инвариант: **автор и его проверяющий на разных моделях** - `implement` и `compliance` обязаны отличаться, сниппет сравнивает их при каждом запуске этих ролей и говорит, если совпали; тогда сверка реализаций codex независимой не считается ("Кто писал, тот не сверяет" в "Когда применять") и уходит Claude, пока профили не разведены. `audit` с любым из них совпадать может: инвариант про пару автор-проверяющий, а не про роли вообще. Решение 11.09.2026 (dwl): основная модель одна и та же у `audit` и `implement` - флагман нового поколения, потому что через `audit` идут самые частые вызовы codex и раньше они шли мимо нее; `compliance` держит предыдущую модель. **Эффорт прогона задает только флаг `-c model_reasoning_effort` по таблице режимов** - ни профиль, ни `config.toml` (его TUI тоже переписывает) на прогоны скилла не влияют; исключение - `resume`, наследующий модель и эффорт исходной сессии. Битое значение в профиле роняет запуск громко (`Error loading config.toml`, rc=1, отчет из двух строк), неизвестные ключи codex терпит (проверено 04.09.2026); какая модель шла на самом деле, показывает rollout - статистика печатает ее строкой `model:`.
   - `-c model_reasoning_effort="<effort>"` - по режиму (доступно: minimal / low / medium / high / xhigh): `medium` у `audit`, `adversarial`, `advocate`, `second-opinion` и `compliance`, `high` у `escalation` и у `adversarial` на необратимом.
   - **`minimal` не использовать** - ломается в codex-cli.
   - **`high` - исключение, а не дефолт.** Он думает минутами и съедает окно лимита быстрее всего: одного глубокого аудита хватает, чтобы от пятичасового окна осталась половина. Ось та же, что в разделе "Как встроить валидацию в поток", - **необратимость**: `high` берется, когда ошибка не откатывается (миграция боевых данных, границы доступа и полномочий, схема данных, механика доставки или удаления файлов) либо когда мы уже застряли (`escalation`, зовется редко). Основания разные, и одно другим не подменяется: у `adversarial` это необратимость объекта, у `escalation` - наблюдаемая трудность (три провалившихся фикса и есть довод), а не глубина ради глубины. Все остальное - `medium`.
   - **Вилка вызывающего - один шаг от дефолта, с названной причиной.** Дефолт из таблицы это точка отсчета, а не приговор: вызывающий может шагнуть на уровень **вниз**, когда объект мал и механичен (`audit` однофайловой правки, второе мнение по записке в пять строк), или на уровень **вверх**, когда объект необратим или велик, - это исключение для `adversarial` выше, обобщенное на все режимы. Шаг считается **от дефолта таблицы режимов**, а не от уровня предыдущего запуска: два шага нельзя, `minimal` нельзя. **`compliance` вниз не шагает**: запрет `low` для него в следующей строке сильнее вилки, размер диффа тут не довод - на пяти строках проза инвариантов нарушается так же, как на пятидесяти. Причина - свойство объекта (размер, необратимость, новизна), а не настроение или спешка, и она пишется первой строкой плана: тогда в отчете видно, почему уровень такой, а не унаследован молча. Причины нет - дефолт.
   - `compliance` - `medium`, а не `low`. Сверка выглядит механикой (пункты против диффа), но там, где у спеки есть проза инвариантов, глубина работает: на SDD-конвейере `medium` поймал четыре нарушенных инварианта при зеленых критериях приемки и разрушительный побочный эффект - то, что тесты по критериям не ловят по построению. Один уровень на режим проще критерия по наличию раздела: критерий агент либо забудет, либо подгонит. `second-opinion` - `medium` по своей природе: ценность в стороннем взгляде, а не в исчерпывающей проверке.
   - Цену занижения стоит знать: на прогоне 29-31.08.2026 состязательное ревью на `high` дало настоящие блокеры в каждом из трех раундов (удаление брифа соседнего проекта, path traversal через непроверенное поле, запись мимо целевой папки). Поэтому у `adversarial` право на `high` сохраняется - но по признаку необратимости, а не по умолчанию.
   - **Снижать `effort` ради экономии бессмысленно, если объем чтения не падает вместе с ним.** На живом пакете прогонов `medium` и `high` съели одинаковые 210-260 тыс. токенов за прогон - расход определило то, сколько файлов промт заставлял перечитывать, а не уровень рассуждения. Экономят пакетирование (общие файлы читаются один раз на несколько единиц вместо одного на единицу) и разделение ролей: дешевая модель на сборе фактов плюс сплошная сверка сильной моделью - без сверки дешевый сбор ошибается в локаторах и обрезает цитаты (наблюдалось 17,9% ошибок, хоть и без выдумывания).
4. **План** класть в `docs/<plan-file>.md` (рабочая заметка, не едет на прод; имя файла - по таблице "Режимы").
5. **Отчет** писать в `docs/<report-file>.md` через shell-редирект, потом читать через `Read`.
6. Bash-таймаут поднимать до 600000ms (10 мин) - codex может думать долго, особенно на `high`.
7. **Всегда** после сессии собирать статистику лимитов аккаунта (см. блок "Статистика лимитов после сессии") - дописывать в конец `<report-file>.md` и показывать пользователю в саммари. Без этого скилл считается выполненным некорректно: непонятно, насколько мы близко к ограничению по тарифу. Статистика по самим токенам (input/output/reasoning) намеренно не собирается - она шум, важны только проценты лимитов.
8. **Всегда** перед запуском codex делать pre-check окна `primary` лимита по последнему rollout (на тарифе с двумя окнами это короткое `5h`, на тарифе с одним - недельное; метка берется из `window_minutes`) (см. блок "Pre-check лимита перед запуском"). Если осталось `< 10%` - НЕ запускать codex; вернуть пользователю алерт с фактическим остатком и временем до сброса, и попросить решить (подождать или все равно запустить). Иначе мы тратим контекст и время на запуск, который завершится частичным ответом или ошибкой лимита. Пустой результат читается **двумя разными состояниями, и вести себя надо по-разному**:
   - **папка codex найдена, rollout-логов в ней нет** - это действительно первый запуск на машине: предупредить пользователя и разрешить запуск;
   - **домашняя папка codex не резолвилась вовсе** - это не первый запуск, а промах резолва: проверка лимита **не выполнялась**, и про остаток квоты мы не знаем ничего. Не рапортовать это тоном штатной ветки - сказать пользователю, что путь не определился и лимит не проверен, и спросить, запускать ли. Резолв - в блоке "Резолв домашней папки codex".
9. **Домашнюю папку codex резолвить, а не брать из окружения** (блок "Резолв домашней папки codex"). Не заменяй сниппеты на `~/.codex` и не пиши `~/.codex` в собственных командах. Одного `"${CODEX_HOME:-$HOME/.codex}"` мало: он защищает от захардкоженного пути, но не от того, что переменная задана в интерактивном профиле пользователя и в неинтерактивный shell агента не доходит - тогда фолбэк берет пустую `$HOME/.codex`, и промах выглядит штатно. Признак - pre-check рапортует "первый запуск на машине" на устройстве, где codex заведомо работал (наблюдалось: `CODEX_HOME=/data/.codex` в профиле, 141 rollout-файл на диске, pre-check отчитался о первом запуске). Промах бьет по трем местам сразу: pre-check рапортует "rollout-логов нет", пост-сбор статистики не находит сессию, сверка `session id` перед `resume` объявляет настоящий id сфабрикованным. Четвертое место - вне проверок скилла, и оно самое дорогое: команда, запущенная руками (`codex exec`, `login status`, `resume`), при том же промахе отвечает так, будто логин слетел (`401 Missing bearer`, `Not logged in`, `no rollout found for thread id` - разбор в "Ограничениях"). Пока резолв не выполнен, о потере авторизации не сообщать и `codex login` не предлагать.
10. **В не-git проекте обязателен `--skip-git-repo-check`.** Перед запуском проверить, git ли проект (`git -C <project-root> rev-parse >/dev/null 2>&1`); если нет - добавлять флаг во ВСЕ вызовы `codex exec`, включая `resume`. Без него codex не стартует вообще, причем молча (rc=0 и отчет из двух строк - см. "Ограничения"). Флаг снимает только проверку trusted-каталога, `-s read-only` продолжает действовать. Актуально для проектов типов wiki и management - это обычно vault'ы и папки заметок без git.

## Шаблон вызова

Сначала **резолв домашней папки codex и модели из профиля роли** (правило 3). Флаг собираем в массив `MODEL_FLAG`; профиля нет - сниппет останавливается, дефолт codex не подставляется:

```bash
if ! { [ -d "$CODEX_HOME/sessions" ] && [ -r "$CODEX_HOME/sessions" ] && [ -x "$CODEX_HOME/sessions" ]; }; then
  CODEX_HOME=$(grep -hs -E '^[[:space:]]*(export[[:space:]]+)?CODEX_HOME=' \
      ~/.bashrc ~/.bash_profile ~/.profile ~/.zshrc ~/.zshenv 2>/dev/null \
    | sed -E "s/.*CODEX_HOME=//; s/^[\"']//; s/[\"']$//; s|^~|$HOME|; s|\\\$HOME|$HOME|g; s|/+$||" \
    | while IFS= read -r d; do [ -n "$d" ] && [ -d "$d/sessions" ] && [ -r "$d/sessions" ] \
        && [ -x "$d/sessions" ] && echo "$d"; done | sort -u)
  [ -z "$CODEX_HOME" ] && [ -r "$HOME/.codex/sessions" ] && [ -x "$HOME/.codex/sessions" ] \
    && CODEX_HOME="$HOME/.codex"
  [ "$(printf '%s' "$CODEX_HOME" | grep -c .)" -gt 1 ] && CODEX_HOME=""
fi
{ [ -d "$CODEX_HOME/sessions" ] && [ -r "$CODEX_HOME/sessions" ] && [ -x "$CODEX_HOME/sessions" ]; } || CODEX_HOME=""
# экспорт - для дочернего процесса (проверкам скилла он не нужен: они читают
# файлы в этом же bash). Пустое значение не экспортируем - не затирать окружение
[ -n "$CODEX_HOME" ] && export CODEX_HOME
PROFILE=audit   # роль этого скилла; сверка реализации codex (compliance после codex-implement) ставит compliance, codex-implement - implement
PROFILE_FILE="$CODEX_HOME/$PROFILE.config.toml"
[ -n "$CODEX_HOME" ] || { echo "PROFILE: папка codex не резолвилась - стоп, см. 'Резолв домашней папки codex'"; exit 1; }
[ -e "$PROFILE_FILE" ] || { echo "PROFILE: нет $PROFILE_FILE - создай файл строкой model = \"<slug>\" и повтори; на дефолт codex скилл не идет"; exit 1; }
read_profile_model() {
  python3 - "$1" <<'PYMODEL'
from pathlib import Path
import sys
import tomllib
try:
    with Path(sys.argv[1]).open('rb') as stream:
        model = tomllib.load(stream).get('model')
    if (not isinstance(model, str) or not model or model != model.strip()
            or any(ord(c) < 32 or ord(c) == 127 for c in model)):
        raise ValueError('Invalid model identity')
except (OSError, ValueError):
    sys.exit(1)
print(model)
PYMODEL
}
PROFILE_MODEL=$(read_profile_model "$PROFILE_FILE")
[ -n "$PROFILE_MODEL" ] || { echo "PROFILE: в $PROFILE_FILE нет читаемого model (права, ключ, TOML) - поломка конфига, стоп"; exit 1; }
MODEL_FLAG=(-p "$PROFILE" -c model="$PROFILE_MODEL")
# инвариант ролей: автор (implement) и его проверяющий (compliance) обязаны отличаться; audit может совпадать с любым
OTHER=""; [ "$PROFILE" = implement ] && OTHER=compliance; [ "$PROFILE" = compliance ] && OTHER=implement
OTHER_MODEL=""; [ -n "$OTHER" ] && OTHER_MODEL=$(read_profile_model "$CODEX_HOME/$OTHER.config.toml")
[ -n "$OTHER" ] && [ -z "$OTHER_MODEL" ] && { echo "PROFILE: нет читаемой модели в $OTHER.config.toml - независимая пара не определена, стоп"; exit 1; }
[ -n "$OTHER_MODEL" ] && [ "$OTHER_MODEL" = "$PROFILE_MODEL" ] && { echo "PROFILE: $PROFILE и $OTHER на одной модели ($PROFILE_MODEL) - независимой сверки нет, стоп"; exit 1; }
```

Строки `PROFILE: ...` - для пользователя: "профиля нет" и "файл есть, но не прочитан" - два разных состояния, оба останавливают запуск; "одна модель у `implement` и `compliance`" и "нет читаемой модели второго профиля пары" тоже останавливают запуск до вызова модели. Роль `audit` не требует пары автоматически, но при сверке проверяется отличие от фактической модели автора. Профиль читается через stdlib `tomllib` (Python 3.11+), не через `source`: TOML должен быть корректным, `model` - непустой строкой без крайних пробелов и управляющих символов. Другие ключи профиля применит сам Codex через `-p`; сравнение строк не доказывает различие CLI-алиасов, фактическая модель подтверждается по запуску. Битое значение в профиле (например, `sandbox_mode = "nonsense"`) роняет запуск громко - `Error loading config.toml`, rc=1, отчет из двух строк; неизвестные ключи codex терпит (проверено 04.09.2026). Какая модель шла на самом деле, показывает rollout, а не наши флаги: пост-сбор статистики печатает ее строкой `model:`.

Затем **проверить, git ли проект**: в не-git каталоге codex откажется стартовать (см. правило 10 и "Ограничения"). Флаг собираем в массив по той же схеме - в git-проекте он пуст и в команду не попадает:

```bash
GIT_FLAG=(); git -C <project-root> rev-parse >/dev/null 2>&1 || GIT_FLAG=(--skip-git-repo-check)
```

Каждый запуск (обычный, отвязанный, resume) - с `-c project_doc_max_bytes=0`: codex не подгружает AGENTS.md проекта. Файлы читать он по-прежнему может.

Дальше подставить массивы в вызов:

```bash
python3 "<toolkit>/scripts/codex-sandbox.py" audit --root "<project-root>" -- \
  -s read-only -c project_doc_max_bytes=0 \
  "${MODEL_FLAG[@]}" \
  "${GIT_FLAG[@]}" \
  -c model_reasoning_effort="<effort>" \
  < docs/<plan-file>.md \
  > docs/<report-file>.md 2>&1
```

**План подается через stdin, а не аргументом.** У агента stdin не терминал, и в этом режиме `codex exec` ждет промт именно оттуда: при вызове с планом-аргументом он печатает `Reading additional input from stdin...` и висит, пока stdin не закроется. Прогон уходит в таймаут целиком - наблюдалось 10 минут впустую при `timeout: 600000`. Форма с аргументом рабочая только в интерактивном терминале, поэтому в скилле ее нет. Если аргумент по какой-то причине нужен, добавляй `< /dev/null`, иначе повиснет то же самое.

Легенда: `"${MODEL_FLAG[@]}"` - `-p audit -c model=<slug из audit.config.toml>` (сверка реализации codex - `-p compliance`, у `codex-implement` - `-p implement`, каждый из своего профиля); `"${GIT_FLAG[@]}"` - `--skip-git-repo-check` в не-git проекте (или ничего в git-проекте); `<effort>` - по таблице "Режимы": `medium` (audit, adversarial, advocate, second-opinion, compliance) или `high` (escalation и adversarial на необратимом); шаг на один уровень от дефолта - только с причиной первой строкой плана (правило 3); `<plan-file>` / `<report-file>` - имена из таблицы "Режимы"; `<project-root>` - абсолютный путь корня текущего проекта, в котором сейчас идет работа.

**Резолв (включая `export CODEX_HOME`) и `codex exec` - в одном Bash-вызове.** Shell-состояние между Bash-вызовами агента не переживает: резолв из предыдущей команды в следующей уже не виден. Без экспорта дочерний codex берет дефолт `~/.codex`, где на машине с нестандартным путем нет авторизации, - и падает в `401`, хотя все проверки скилла (pre-check, резолв модели) отработали штатно в том же bash.

Если план уже в переменной или буфере, а не в файле - тот же вызов, только stdin из here-string: `<<< "$PLAN"` вместо `< docs/<plan-file>.md`.

### Опционально: resume существующей сессии

Для SDD-compliance этот шаблон применяется только к правкам из исходного отчета при исходном `medium`. Другой effort или новый вопрос - новая проверяющая сессия с исходной спекой и отчетом; общий совет о наследовании глубокого разбора ниже этот гейт не отменяет.

Сессию, начатую без флага, не продолжай - запускай заново.

Для повторного раунда по той же теме (re-review после применения фиксов, уточнение мутного ответа, догрузка вопросов) можно переиспользовать сессию Codex через `resume <SESSION_ID>` - тогда Codex не нужно заново скармливать план, файлы и фрейминг, он помнит контекст первого прохода. Экономит входной токен-бюджет и сохраняет преемственность анализа.

```bash
python3 "<toolkit>/scripts/codex-sandbox.py" audit --root "<project-root>" -- -s read-only -c project_doc_max_bytes=0 "${GIT_FLAG[@]}" \
  resume "$SESSION_ID" \
  < docs/<followup-plan-file>.md \
  > docs/<followup-report-file>.md 2>&1
```

> **Порядок флагов.** `-C` подставляет скрипт. Флаги codex после `--` (`-s`, `-c`, `--skip-git-repo-check`) ставь до подкоманды `resume`; после нее codex принимает только аргументы продолжения.

`SESSION_ID` берется из шапки предыдущего отчета (Codex по нашей просьбе пишет `session id: <id>` первой строкой - см. "Упаковка контекста в план"):

```bash
SESSION_ID=$(awk -F': ' '/^session id:/ {print $2; exit}' docs/<previous-report-file>.md)
```

**Шапке отчета не доверяй вслепую** - модель может сфабриковать id (наблюдалось: в отчете UUID, которого нет ни в одном rollout). Перед resume сверь, что файл `$CODEX_HOME/sessions/*/*/*/rollout-*-<SESSION_ID>.jsonl` существует (`CODEX_HOME` - через резолв из блока "Резолв домашней папки codex", иначе сверка объявит настоящий id сфабрикованным); нет - настоящий id достань из имени свежайшего rollout-файла (`ls -1t "$CODEX_HOME"/sessions/*/*/*/rollout-*.jsonl | head -1`). Папка не резолвилась - сверять нечем: скажи это прямо, а не выдавай "id не найден". Учти при этом, что codex в multi-agent-режиме пишет рядом с основной сессией rollout'ы **субагентов** (таймстампы почти совпадают, и свежайшим может оказаться именно субагентский): resume субагентского id падает с `direct app-server input is not allowed for multi-agent v2 sub-agents (code -32600)`. Поймал эту ошибку - не перебирай соседние файлы той же минуты (наблюдалось, что субагентскими оказываются все); надежный кандидат - id, которым сессия запускалась изначально (из первого запуска или прошлого resume - он остается resumable через все раунды).

Особенности resume:
- `-c model="..."` и `-c model_reasoning_effort="..."` в resume **игнорируются** - resume наследует модель и effort исходной сессии. Вне SDD-compliance это допустимо для re-review того же режима; для SDD действует ограничение `medium` и правок исходного отчета выше, но именно отсюда молчаливый перерасход: продолжение глубокого аудита короткой консультацией тратит `high` на обычную развилку. Дефолт и обмен - в разделе "Как встроить валидацию в поток"; нужен другой уровень - стартуй свежую сессию, не resume. Следствие для выбора модели: первую сессию открывай той моделью, которой хочешь вести все раунды, - `resume` ее не сменит.
- Просьба про `session id` в новом плане **не нужна** - sessionID у продолженной сессии тот же; пост-сбор лимитов работает по тому же `SESSION_ID` (можно его же переиспользовать или достать из новой шапки, если Codex ее напишет).
- Pre-check лимита перед resume - такой же обязательный, как перед новым запуском: короткое окно могло быть исчерпано предыдущим раундом.
- Для каждой итерации используй отдельные имена файлов плана/отчета (`<plan-file>-r2.md`, `<report-file>-r2.md`, дальше `-r3` и т.д.), не перезаписывай предыдущие - история раундов нужна для разбора.

### Опционально: фоновый запуск для долгих аудитов

`codex exec` синхронный и на `high` может думать минутами, блокируя сессию Claude. Прежняя редакция называла `run_in_background: true` у Bash-тула надежным способом не ждать. **Для долгих прогонов это не так**: харнесс снимает фоновую задачу на середине - дважды на обкатке `codex-implement` 04.09.2026 (`status: killed` без причины) и в тот же день в сессии канона (вывод задачи `[killed]`, в журнале ядра за три дня ни одного OOM - убила не система). Симптом тихий: отчет пуст или оборван, ошибки нет, rollout есть - наполовину (`rules/silent-failure.md`).

Поэтому: **прогон в несколько минут - foreground с `timeout: 600000`**, ждать его дешевле, чем разбирать снятый. **Прогон дольше - только отвязанным процессом по протоколу ниже.** `run_in_background` для `codex exec` не использовать.

Независимо от формы запуска **stdin обязан быть закрыт или занят планом** - иначе без TTY codex ждет "additional input from stdin" до EOF, которого не будет, и виснет с пустым отчетом (наблюдалось зависание почти на 7 часов; "процесс жив" - не признак прогресса, проверяй, что отчет растет). Форма из "Шаблона вызова" (`< <plan-file>`) это закрывает сама. Поймал зависание (отчет не растет, в нем "Reading additional input from stdin...") - убей процесс и перезапусти план через stdin; потерь нет, зависшая сессия фактически не начиналась. Pre-check лимита - до запуска, как обычно.

### Отвязанный запуск - протокол

Отвязка - не одна команда, а набор свойств, каждое из которых закрывает свой отказ. Тот же механизм в `rules/scheduled-automation.md` для вложенного `claude -p`; принцип там же: процесс, чей результат должен пережить вызов тула или сессию, нуждается во внешнем владении жизненным циклом - журнале, PID и проверяемом статусе завершения. Отвязывать только такое: прогон codex - да; миграцию, сборку или тесты с записью, которые должны отмениться вместе с сессией, - нет.

- **Свой каталог запуска на каждый прогон**: `docs/codex-runs/<topic>-<дата-время>/` - в нем план, отчет, `pid`, лог обвязки и маркер `exit`. Старый маркер не может быть принят за новый: у нового прогона свой каталог.
- **`setsid nohup`, stdin из плана, stdout в отчет** - процесс в собственной сессии, харнесс его не снимает. PID пишет **сам дочерний скрипт** (`echo $$`), а не `$!` снаружи: `setsid` из фонового задания форкается, и `$!` указывает на уже завершившуюся обертку - проверено 04.09.2026, группа по нему не снималась.
- **Маркер завершения атомарный и с кодом выхода**: пишется во временный файл и переименовывается. "Файл `exit` есть" - единственный признак конца; "процесса нет в `ps`" им не является. Отмена тоже оставляет маркер: `trap` на `TERM` пишет `cancelled` (проверено - без него убитая группа маркера не оставляет, и ожидание не кончается никогда). Ненулевой код, `cancelled` или отсутствие маркера - **не результат**: отчет не читать как готовый, к следующему шагу не переходить.
- **Команда отмены известна до запуска**: `kill -TERM -- -$(cat "$RUN/pid")` - группа процессов, а не один PID (после `setsid` они совпадают, и `pid` записан изнутри). Отвязанный процесс переживает сессию Claude: отменил задачу пользователь - процесс снимается этой командой, сам он не остановится.
- **Ожидание - инструментом ожидания условия** (`Monitor` в Claude Code) по появлению файла `exit`, не `sleep` в цикле и не перечитывание отчета.
- **Заподозрил обрыв по лимиту раньше маркера и проверяешь растущий отчет** - искать точную привязанную строку `^ERROR: You've hit`, а не слово "лимит"/"usage limit": codex может процитировать эти слова в собственном отчете о прогрессе, и грепп по слову тогда даст ложную тревогу.
- **Резолв `CODEX_HOME` и `export` - в том же Bash-вызове**, до `setsid`: дочерний процесс наследует окружение, между вызовами оно не живет.
- **Preflight до `setsid`**: каталог создан, план скопирован, `setsid` есть - любой отказ останавливает запуск, а не идет дальше с пустым отчетом. Суффикс `-$$` в имени каталога снимает коллизию двух запусков в одну секунду. **Рукопожатие после `setsid`**: `pid` ждется ограниченно (до 10 с), проверяется, что это число и что процесс - лидер своей группы; иначе запуск не считается управляемым и разбирается как сбой, а не объявляется успешным с `pid ?`.

```bash
<блок резолва CODEX_HOME / MODEL_FLAG (профиль роли) / GIT_FLAG из "Шаблона вызова", с export CODEX_HOME>
PROJECT_ROOT="<project-root>"   # абсолютный путь, в кавычках: пробел в пути иначе разорвет аргументы
RUN="docs/codex-runs/<topic>-$(date +%Y%m%d-%H%M%S)-$$"; mkdir -p "$RUN" || exit 1
cp "docs/<plan-file>.md" "$RUN/plan.md" || { echo "плана нет - стоп"; exit 1; }
command -v setsid >/dev/null || { echo "setsid нет - установи util-linux"; exit 1; }
setsid nohup bash -c '
  trap '"'"'echo cancelled > "$0/exit.tmp" && mv "$0/exit.tmp" "$0/exit"; exit 143'"'"' TERM
  echo $$ > "$0/pid"
  python3 "<toolkit>/scripts/codex-sandbox.py" audit --root "$1" -- -s read-only -c project_doc_max_bytes=0 "${@:2}" < "$0/plan.md" > "$0/report.md" 2>&1 & wait $!
  rc=$?; echo "$rc" > "$0/exit.tmp" && mv "$0/exit.tmp" "$0/exit"
' "$RUN" "$PROJECT_ROOT" "${MODEL_FLAG[@]}" "${GIT_FLAG[@]}" -c model_reasoning_effort="<effort>" \
  > "$RUN/nohup.log" 2>&1 < /dev/null &
for i in 1 2 3 4 5 6 7 8 9 10; do [ -s "$RUN/pid" ] && break; sleep 1; done
PID=$(cat "$RUN/pid" 2>/dev/null); case "$PID" in ''|*[!0-9]*) echo "PID не получен за 10 с - запуск не управляем, разбирай $RUN/nohup.log"; exit 1;; esac
[ "$(ps -o pgid= -p "$PID" | tr -d ' ')" = "$PID" ] || { echo "pid $PID не лидер группы - отмена группой не сработает"; exit 1; }
echo "запущен: $RUN, pid $PID; отмена: kill -TERM -- -$PID"
```

`<topic>` в имени каталога - слаг из `[a-z0-9-]`, не фраза пользователя как есть: `/` и `..` в нем уводят каталог прогона из `docs/codex-runs/`, и это делает внешний shell, до песочницы codex.

Для `codex-implement` та же внешняя обвязка `setsid`, внутри - команда скрипта из его шага 4; `PROFILE=implement` в резолве. Если задание порождает промежуточные файлы (черновики, побочные результаты), в плане явно называть каталог назначения (например, `_рабочие/`) - без этого codex сорит черновиками прямо в корень результата: наблюдалось до полусотни файлов за один прогон.

**После маркера - извлечь итоговый ответ.** `$RUN/report.md` - это весь stdout `codex exec`: шапка, план, трассировка вызовов инструментов и рассуждений, и только в конце ответ. На живых аудитах это 7-31 тысяч строк при ответе в 200-300 (cactus-adm, 05.09.2026; в сессии канона 04.09 - 5400 строк при 200). Читать такой файл `Read` нельзя: целиком не влезет, а по частям затянет в основной контекст трассировку. Маркеры устойчивы (проверено на codex 0.145 и 0.153): ответ начинается после **последней** строки `codex` (метка хода модели, их несколько - по числу ходов) и кончается перед **первой после нее** строкой `tokens used` (после нее тело ответа может повториться целиком - потоковая выдача плюс финальная, первый `tokens used` снимает дубль). Настоящий `session id` стоит в шапке, которую печатает сам codex, и берется **до** обрезки: в теле ответа модель его выдумывает.

```bash
L=$(grep -n '^codex$' "$RUN/report.md" | tail -1 | cut -d: -f1)
T=$(awk -v l="$L" 'NR>l && /^tokens used/ {print NR; exit}' "$RUN/report.md")
SID=$(awk -F': ' '/^session id:/ {print $2; exit}' "$RUN/report.md")   # шапка codex, не тело ответа
[ -n "$L" ] && [ -n "$T" ] || { echo "маркеров нет - отчет оборван, это не результат"; exit 1; }
{ echo "session id: $SID"; sed -n "$((L+1)),$((T-1))p" "$RUN/report.md"; } > "docs/<report-file>.md"
```

Дальше все штатно: `docs/<report-file>.md` - обычный отчет режима с `session id` первой строкой, статистика лимитов дописывается в него сниппетом "Статистика лимитов после сессии" без правок, шаг 6 "Процесса" читает его. `$RUN/report.md` остается полным логом прогона для разбора. Каталог `docs/codex-runs/` должен быть в `.gitignore` проекта; если правила еще нет, добавь его перед первым отвязанным запуском. Логам по 30 тысяч строк в репозитории не место.

## Как codex выбирает модель

Порядок приоритета (от высшего к низшему):
1. `-m <model>` / `--model <model>` или `-c model="..."` в командной строке - переопределяет все.
2. `-p <name>` - профиль-файл `$CODEX_HOME/<name>.config.toml`, наложенный поверх `config.toml` (codex 0.145; прежняя форма `[profiles.<name>]` внутри `config.toml` в этой версии не действует). Так подключается модель аудитных режимов - профиль `audit`, а для сверки реализации codex - `compliance` (правило 3).
3. `config.toml` в домашней папке codex (где она - см. "Резолв домашней папки codex"; своими командами путь не собирать) - глобальный дефолт.

В этом скилле модель всегда идет пунктом 2 - профилем роли (`audit` здесь, `compliance` на сверке реализации codex, `implement` у `codex-implement`), и пункт 3 в прогоны скилла не попадает никогда: `config.toml` принадлежит интерактивному codex и переписывается выбором в его меню. Slug из профиля дополнительно передается явным `-c model` (пункт 1, то же значение), чтобы он попал в лог и саммари. Номера версий скилл не хардкодит: они живут в трех файлах профилей на машине (правило 3).

### Как сравнить модели

Список моделей codex меняется с релизами, и профиль `audit` держится на последнем замере, а не на позиционировании модели в меню. Замер - штатная процедура, не разовое:

- тот же план, тот же worktree, тот же effort (`medium`), по одной новой сессии на модель;
- сравнивать три главных находки и время; отчеты рядом в `docs/`. **Расход - только у последовательных прогонов**: окно лимита общее на аккаунт и отдается целыми процентами, у параллельных сессий доля каждой не восстанавливается (три параллельных прогона 05.09.2026 подняли недельное окно на 4% вместе, и кто сколько - неизвестно). Параллельный замер сравнивает качество и время, расход по нему не считается;
- объекты разных классов, не одна спека: `compliance` по диффу, код, полномочия и безопасность, `adversarial`. Один замер на одной спеке - первоначальный дефолт, а не доказательство;
- результат - в профиль соответствующей роли (`audit.config.toml` для аудита, `implement.config.toml` для реализации, `compliance.config.toml` для сверки реализаций codex; `implement` и `compliance` обязаны остаться разными) и в память записью с датой и цифрами, чтобы следующий замер сравнивал с прошлым.

Ориентир с замера 05.09.2026 (cactus-adm, состязательный аудит доменной спеки, две новые сессии на `medium`): флагман нового поколения дал отчет полнее и точнее прежней модели профиля при том же времени - 20 нарушений против 18, все уникальные находки подтверждены по коду против одной ложной атрибуции. При этом два инварианта поймала только прежняя модель: отчеты дополняют друг друга, и на спеке с высокой ценой ошибки второй проход другой моделью по тому же плану - дешевый способ добрать пропуски.

Модель на реализацию - отдельным замером в `codex-implement`: там цена ошибки другая.

## Процесс

1. **Определить режим** (`audit` / `adversarial` / `advocate` / `second-opinion` / `compliance` / `escalation`) - от него зависят `effort`, фрейминг плана, формат ответа и имена файлов (таблица "Режимы").
2. **Pre-check лимита** (см. блок "Pre-check лимита перед запуском"). Если окно `primary` (`5h` или недельное - по тарифу) показывает `left < 10%` - НЕ запускать codex, вернуть пользователю алерт с остатком и временем до сброса, спросить как поступить. Если папка codex найдена, но rollout-логов нет - предупредить и идти дальше; если папка не резолвилась - лимит не проверен, сказать это пользователю и спросить (правило 8).
3. **Сформулировать план** - записать в `docs/<plan-file>.md`. План самодостаточный (Codex не видит историю чата): по чеклисту "Упаковка контекста в план". Фрейминг и формат ответа - под выбранный режим.
4. **Запустить codex** по шаблону выше, `timeout: 600000`; прогон дольше нескольких минут - отвязанным процессом ("Отвязанный запуск - протокол"), не `run_in_background`.
5. **Собрать статистику лимитов** (см. блок "Статистика лимитов после сессии") - дописать в конец `<report-file>.md`.
6. **Прочитать отчет** через `Read docs/<report-file>.md`. После отвязанного прогона - сперва извлечь ответ из `$RUN/report.md` в этот файл (сниппет в "Отвязанный запуск - протокол"), полный лог `Read` не открывать.
7. **Разобрать находки** (см. блок "Разбор находок") - верифицировать каждую, разделить на "применил / предлагаю / нужно решение". Кратко изложить пользователю (3-5 пунктов) + блок "лимиты" по строке на каждое окно, которое отдает rollout (два на тарифе с `5h` и `1w`):
   - `<окно>: left N% (used M%), reset [D day ]H:MM hour` (целые сутки + часы:минуты до конца окна);
   - если по любому окну `left < 20%` - отдельной строкой над блоком вынести предупреждение "близко к лимиту по окну <окно>".
   Спросить, какие пункты брать в работу. Не браться за все молча.
8. После завершения задачи решить судьбу файлов: если задача закрыта, можно удалить `<plan-file>` и `<report-file>` или сослаться на них из соответствующего todo.

## Упаковка контекста в план

Каждый вызов Codex изолирован - план обязан быть самодостаточным. Минимум (адаптируй под режим):

- **Проблема / цель** - 1-2 предложения: что проверяем (`audit`), что пытаемся сломать (`adversarial`), какое решение/подход оцениваем (`second-opinion`), что сверяем с планом (`compliance`), где застряли (`escalation`).
- **Контекст** - 2-5 строк: что за фича, почему сейчас этим занимаемся.
- **Ключевые файлы** - конкретные пути с номерами строк (Codex видит репо, но укажи куда смотреть), не общие "посмотри в app/". Для ревью большого PR/диффа НЕ вставляй в план полный патч - дай список `git diff --name-only <base>..HEAD` плюс 2-3 хот-спота с номерами строк; Codex сам подтянет нужное через `-C`. Это экономит контекст ревьюера и снижает риск упереться в окно.
- **Правила проекта - текстом в план.** Автоматическая загрузка `AGENTS.md` отключена через `-c project_doc_max_bytes=0`. Необходимые нормы (стиль, конвенции, ограничения проекта) передавай прямо текстом в плане; не рассчитывай на автоматическое чтение `AGENTS.md` или `CLAUDE.md`.
- **Что уже пробовали** (особенно для `escalation`) - гипотезы/фиксы и почему не сработали.
- **Evidence** - точные ошибки, стектрейсы, вывод тестов.
- **Ожидаемый результат / формат ответа** - что значит "готово"; попросить отвечать структурно по формату режима (`audit` -> вердикт ok/risk/blocker; `adversarial` -> список атак с серьезностью, "проблем нет" только с перечнем проверенного; `advocate` -> по каждому пункту нашего вывода вердикт справедлив / преувеличен / несправедлив / не проверяем, со счетом по вердиктам в конце; `second-opinion` -> согласие + альтернативы + трейдоффы; `compliance` -> таблица "пункт плана -> статус -> комментарий" + блок "сверх плана"; `escalation` -> диагноз + root cause + подход).
- **`session id` первой строкой** - попросить Codex первой строкой отчета написать `session id: <id>` (нужно для пост-сбора статистики лимитов). Использовать его без сверки нельзя - см. "Шапке отчета не доверяй вслепую" в секции resume.
- **Делегирование субагентам** - отдельным блоком в начале плана (шаблон ниже).

### Шаблон плана для `advocate`

У этого режима вход и установка обратные остальным, поэтому план строится иначе: объект идет **приложением**, а разбирается наш собственный вывод.

```text
Ты - адвокат объекта. Установка: **исходи из того, что наш вывод о нем необоснован**,
и опровергай ЕГО, а не объект. Твоя задача не найти новые дефекты объекта, а
проверить, справедливо ли то, что мы уже написали.

Вход: наш разбор и сам объект. По КАЖДОМУ пункту разбора - вердикт:
справедлив / преувеличен / несправедлив / не проверяем. В конце - счет
по вердиктам.

## Наш разбор (проверяется)

<ВСТАВЬ СЮДА РАЗБОР ЦЕЛИКОМ, по пунктам>

## Объект (приложение, для сверки)

<ВСТАВЬ СЮДА ОБЪЕКТ ИЛИ ПУТЬ К НЕМУ В РЕПО>

Проверяй по каждому пункту разбора:
- жанр объекта: не требуем ли того, чего жанр не предполагает;
- противоречие требований: не просим ли одновременно взаимоисключающего;
- норма предметной области: не названо ли дефектом то, что здесь принято;
- точность цитирования: не атакуем ли формулировку сильнее написанной в объекте;
- додуманный контекст: не истолковали ли за автора его цель или ограничения;
- операциональность: понятно ли по находке, что менять (вывод без критерия
  адресату бесполезен и проверке не поддается);
- двойной счет: не разложена ли одна проблема на несколько пунктов;
- проверяемость: не предъявлено ли как факт то, чего мы знать не можем.
```

**Условие чистоты: возражения, уже поступившие от автора объекта или заказчика, адвокату не подаются.** Иначе он их пересказывает, и проверка перестает отвечать на главный вопрос - ловится ли проблема заранее. Ценность прецедента, из которого взят режим, ровно в этом: адвокат независимо воспроизвел все пять возражений клиента, не видя их.

**Когда звать.** Перед отправкой разбора наружу (клиенту, заказчику, автору кода); когда два прохода "подозрительно сошлись" - сначала проверь, не ставили ли им одну задачу; в ревью, где дорог ложноположительный вывод. Не нужен там, где вывод дешево проверяется фактом: упавший тест не нуждается в адвокате.

### Шаблон блока про субагентов

Вставлять в начало плана дословно или близко к тексту:

```text
## Использование субагентов

По возможности делегируй вспомогательную работу собственным субагентам на более дешевых моделях:
- поиск файлов/символов, grep по репо;
- чтение больших файлов и выборка релевантных фрагментов;
- сбор фактов (версии, конфиги, наличие/отсутствие чего-либо);
- проверка тривиальных условий ("есть ли тест на X", "используется ли функция Y").

Что НЕ делегировать дешевым моделям:
- финальные вердикты (ok / risk / blocker);
- рассуждения о безопасности, корректности логики, инвариантах;
- сводное заключение в конце отчета.

Главный критерий - качество итогового вывода. Если задача требует основной модели для сохранения качества - используй основную модель, не экономь.
```

## Шаблоны промптов

Подставить в `<plan-file>` (дополнив контекстом по чеклисту "Упаковка контекста"). Во всех режимах в конце - `READ-ONLY: только отчет, файлы не менять` и просьба про `session id` первой строкой.

### Аудит

```text
Аудит кода (read-only).

Цель: [что проверить - одна фраза]
Контекст: [2-5 строк]
Файлы: [пути с номерами строк]
Вопросы:
1. [...]
2. [...]

Формат: по каждому вопросу - вердикт ok / risk / blocker + 1-2 предложения обоснования.
Каждый risk/blocker - с точной привязкой: файл:строка + короткая цитата фрагмента,
который мотивирует находку. Находка без привязки помечается словом unverified.
READ-ONLY: только отчет, файлы не менять. Первой строкой отчета: session id: <id>.
```

### Состязательное (adversarial) ревью

```text
Состязательное (adversarial) ревью (read-only).

Ты adversarial-ревьюер. Исходи из того, что это решение СОДЕРЖИТ скрытый
дефект - твоя задача найти его, а не подтвердить корректность. Не одобряй,
ломай уверенность в решении.

Что ревьюим: [код / дифф / план / спецификация - что именно]
Контекст: [2-5 строк]
Файлы: [пути с номерами строк]

Для каждого компонента ответь:
- при каких входах / состояниях / гонках / нагрузке это сломается?
- какие инварианты и предусловия предполагаются, но не гарантируются?
- где автор рассчитывал на happy path и не закрыл отказ?
- какие неявные допущения рухнут при изменении окружения / данных / масштаба?

НЕ репортить (scope exclusions):
- стиль, нейминг, форматирование, докстринги, комментарии;
- спекулятивные улучшения ("можно было бы вынести в helper", "лучше переименовать", "стоит сделать конфигурируемым");
- повторы кода без functional impact;
- "не идиоматично" без конкретного риска;
- опечатки в комментариях / строках логов;
- предложения добавить тесты / документацию / телеметрию (это не атака).
Цель режима - только то, что может реально сломаться, открыть уязвимость или
привести к некорректному поведению. Шум снижает доверие к находкам.

Формат: список атак, по каждой - что атаковал, чем ломается (или почему
устояло), серьезность, и точная привязка: файл:строка + короткая цитата
фрагмента, на котором атака строится. Атака без привязки помечается unverified.
Вердикт "проблем нет" допустим ТОЛЬКО если перечислил,
что конкретно проверял и почему атака не прошла.
READ-ONLY: только отчет, файлы не менять. Первой строкой отчета: session id: <id>.
```

### Второе мнение (по диффу/изменению)

```text
Независимое второе мнение (read-only).

Что оцениваем: [PR / ветка / незакоммиченные изменения / подход]
Файлы: [ключевые пути]
Что уже сделал основной агент: [кратко, или "проблем не нашел"]

Нужно: независимый взгляд - согласен ли с подходом, какие альтернативы, какие баги / edge-case / логические ошибки мог пропустить основной агент.
Формат: согласен / не согласен по пунктам + альтернативы + трейдоффы. Чеклист блокеров не нужен.
READ-ONLY: только отчет, файлы не менять. Первой строкой отчета: session id: <id>.
```

### Сверка кода с планом (compliance)

```text
Сверка реализации с исходным планом (read-only).

Исходный план:
[вставить план целиком ИЛИ путь к файлу плана в репо - тогда Codex прочитает сам]

Что реализовано:
[git ref/ветка/дифф ИЛИ список затронутых файлов с описанием объема; для большого диффа - `git diff --name-only <base>..HEAD` плюс 2-3 хот-спота с номерами строк]

Нужно: по каждому пункту плана определить статус - реализовано / частично / пропущено / отклонено (с пояснением, в чем отклонение). Дополнительно перечислить, что есть в диффе сверх плана (не было заявлено, но появилось).

Формат: таблица "пункт плана -> статус -> комментарий" + отдельный блок "сверх плана". Без оценок качества кода (это не аудит) - только сверка факта реализации.
READ-ONLY: только отчет, файлы не менять. Первой строкой отчета: session id: <id>.
```

### Эскалация при отладке

```text
Эскалация отладки (read-only).

Проблема: [1-2 предложения]
Файлы: [ключевые пути]
Проверенные гипотезы:
1. [гипотеза] -> [результат]
2. [гипотеза] -> [результат]
Evidence: [ошибка / вывод тестов / стектрейс]

Нужно: диагноз root cause с доказательствами + рекомендованный подход к фиксу.
READ-ONLY: только отчет, файлы не менять. Первой строкой отчета: session id: <id>.
```

### Заблокированный субагент

```text
Делегированный субагент застрял (read-only).

Задача: [название]
Блокер: [что субагент сообщил]
Контекст: [что требует задача]
Уже пробовали: [перезапуск с контекстом, апгрейд модели и т.д.]

Нужно: диагноз блокера + рекомендованный подход.
READ-ONLY: только отчет, файлы не менять. Первой строкой отчета: session id: <id>.
```

### Ревью плана / фазы

```text
Ревью плана перед выполнением (read-only).

Суть плана: [что собираемся делать - кратко]
Затрагиваемые файлы: [список]
Известные риски: [о чем уже думаешь]

Нужно: ревью на design-флоу, регрессии, проблемы интеграции, пропущенные шаги.
Формат: согласен / риски / блокеры по пунктам.
READ-ONLY: только отчет, файлы не менять. Первой строкой отчета: session id: <id>.
```

## Разбор находок

Codex не видит контекст сессии и **может ошибаться**. Не доверять слепо.

1. **Сначала независимо верифицируй каждую находку** в коде (Read/Grep) - Codex не видел контекст сессии, мог ошибиться. Пометь оценку: подтверждаю / сомневаюсь / не согласен. Находка без цитируемой мотивирующей строки (помечена unverified, либо указанный файл:строка не содержит того, что утверждается) - по умолчанию "сомневаюсь": не поднимать как подтвержденную и не чинить, пока сам не нашел подтверждение в коде.
2. **Технические находки** (баги, security, race, performance, API misuse) - сначала верифицируй. В SDD-compliance передай подтвержденные правки автору реализации, затем повтори сверку; вне конвейера применяй в своей сессии по разрешенной области. Codex-проверяющий read-only, файлы не правит.
3. **К пользователю иди только за продуктовым решением**: бизнес-логика, scope ("баг или фича?"), приоритет, trade-off ("чинить сейчас или отложить?").
4. **В итоговом резюме пользователю раздели на три группы**: "проверил и применил / предлагаю / нужно твое решение".
5. **Хвост под режим**:
   - `audit`: **blocker** - обсудить с пользователем, не править молча; **risk** - поднять в саммари, дать выбрать; **ok** - упомянуть одной строкой.
   - `adversarial`: каждую найденную атаку верифицируй в коде - Codex со скептической установкой склонен к ложным тревогам (репортит дефект там, где инвариант на деле держится). Подтвержденные - в группы "применил / предлагаю"; неподтвержденные - отметь как проверенные и отклоненные, не тащи в саммари как реальные. **Опциональный re-review после фиксов:** adversarial-фиксы часто переписывают тонкие места (race, валидация, error handling), и сам фикс может породить новый дефект или закрыть атаку не полностью. После применения подтвержденных находок уместно дернуть второй проход Codex'а только на дифф фиксов по полному защищенному шаблону из раздела "Опционально: resume существующей сессии" - Codex помнит контекст исходного ревью и проверяет, что атаки реально закрыты и фиксы не сломали соседних инвариантов. Решение делать второй проход - **за пользователем, не молча**: после первого раунда предложи в саммари, перечислив, какие фиксы будут проверяться. Pre-check лимита перед re-review - обязательный.
   - `advocate`: вердикты - не приговор, а вход для решения. "Несправедлив" и "не проверяем" убирай из вывода до отправки: первое портит отношения с адресатом, второе выдает за факт то, чего мы знать не можем. "Преувеличен" - переформулируй по факту, а не выбрасывай. Считай долю справедливых: если она мала, проблема не в отдельных пунктах, а в рамке разбора, и его надо пересобирать, а не править построчно.
   - `second-opinion`: вынести согласие/расхождения и альтернативы; это advisory, а не чеклист на закрытие.
   - `compliance`: пропуски и отклонения - к пользователю (продуктовое решение: доделать сейчас, отложить, зафиксировать в TODO, признать осознанным отклонением). Блок "сверх плана" - проверить самостоятельно: это могло быть оправданное доработкой, а могло быть scope creep; подсветить пользователю при втором случае.
   - `escalation`: верифицировать диагноз прежде чем строить фикс; если root cause подтвердился - применить рекомендованный подход; если Codex не помог - эскалировать к пользователю, привести findings обоих.
6. Если codex ушел в сторону или дал слишком общий ответ - уточнить план и перезапустить, не принимать мутный ответ как готовый.

## Дисциплина итераций (сходимость)

Цикл "ревью -> фиксы -> re-review" без явных стоп-условий не сходится: фиксы тонких мест порождают новые находки, а скептическая установка codex всегда найдет, что назвать дефектом. Правила, выведенные из практики:

- **Критерий сходимости - в первый же план.** Каждый adversarial/verify-план завершай инструкцией: "остаточный риск, требующий стека из 3+ независимых маловероятных условий, называй остаточным/приемлемым, а не находкой". Без нее находки мельчают, но не кончаются.
- **Бюджет раундов - по умолчанию 2** (ревью + verify фиксов; verify - это re-review, который по "Разбору находок" п.5 предлагается пользователю, бюджет этот вопрос не отменяет, а ограничивает цикл сверху). Третий и дальше - только явным решением пользователя; в саммари второго раунда дай свою рекомендацию (сошлось / есть незакрытый реальный риск) и перечисли остаточные.
- **Фиксы копить пачкой, ревью гонять параллельно.** Не ревьюить каждый инкремент отдельно: собери фиксы раунда в один дифф; prompt-reviewer (если правится промт) и codex - параллельно на одном диффе, не цепочкой. Ожидание codex (5-15 мин на прогон) - главная статья wall-clock.

## Резолв домашней папки codex

Все проверки скилла (pre-check лимита, пост-сбор статистики, сверка `session id`) читают `sessions/` и `config.toml` из домашней папки codex. Ее расположение переопределяется переменной `CODEX_HOME`, и **написать `"${CODEX_HOME:-$HOME/.codex}"` недостаточно**: переменная обычно задана в интерактивном профиле пользователя и в неинтерактивный shell агента не приходит. Тогда фолбэк честно берет `$HOME/.codex`, там пусто - и проверка молча превращается в "логов нет, первый запуск на машине".

Поэтому папку **резолвим**, а не берем из окружения. Сниппет вставляй в начало каждого блока, где путь нужен: shell-состояние между вызовами не переживает, экспорт из прошлой команды в следующей уже не виден. Блок, который сам запускает `codex exec`, добавляет к сниппету строку `export CODEX_HOME` (см. "Шаблон вызова"): проверкам скилла экспорт не нужен (читают файлы в том же bash), а дочернему процессу codex - обязателен, без него он уходит в дефолт `~/.codex` и падает в `401`.

```bash
if ! { [ -d "$CODEX_HOME/sessions" ] && [ -r "$CODEX_HOME/sessions" ] && [ -x "$CODEX_HOME/sessions" ]; }; then
  CODEX_HOME=$(grep -hs -E '^[[:space:]]*(export[[:space:]]+)?CODEX_HOME=' \
      ~/.bashrc ~/.bash_profile ~/.profile ~/.zshrc ~/.zshenv 2>/dev/null \
    | sed -E "s/.*CODEX_HOME=//; s/^[\"']//; s/[\"']$//; s|^~|$HOME|; s|\\\$HOME|$HOME|g; s|/+$||" \
    | while IFS= read -r d; do [ -n "$d" ] && [ -d "$d/sessions" ] && [ -r "$d/sessions" ] \
        && [ -x "$d/sessions" ] && echo "$d"; done | sort -u)
  [ -z "$CODEX_HOME" ] && [ -r "$HOME/.codex/sessions" ] && [ -x "$HOME/.codex/sessions" ] \
    && CODEX_HOME="$HOME/.codex"
  [ "$(printf '%s' "$CODEX_HOME" | grep -c .)" -gt 1 ] && CODEX_HOME=""
fi
{ [ -d "$CODEX_HOME/sessions" ] && [ -r "$CODEX_HOME/sessions" ] && [ -x "$CODEX_HOME/sessions" ]; } || CODEX_HOME=""
```

На выходе `CODEX_HOME` либо указывает на папку с `sessions/`, либо **пуст - это отдельное состояние "не нашли"**, и трактовать его как первый запуск нельзя (см. правило 8). Свойства, которые важно сохранить при любой переработке сниппета:

- **кандидат проверяется тройкой `-d` + `-r` + `-x` на `sessions/`**, и каждая часть закрывает свой промах. Без `-d` кандидатом становится обычный файл с таким именем. Без `-r` проходит папка, которую нельзя прочитать. Без `-x` проходит папка, которую можно перечислить, но нельзя обойти вглубь - а rollout лежит на три уровня ниже (`sessions/<год>/<месяц>/<день>/`), и глоб вернет пусто. Любой из этих промахов дает один и тот же результат: логов "нет", и предохранитель квоты вырождается в ложный "первый запуск". Одного `ls` для этого мало - он успешен во всех трех случаях. Пустая, но полноценная `sessions/` тройку проходит: это и есть настоящий первый запуск;
- **несколько РАЗНЫХ живых кандидатов - тоже "не нашли"**, а не повод взять первый: в профилях бывает старое определение рядом с актуальным, и молча выбранный не тот путь хуже честного "не определился" - pre-check покажет квоту чужой установки, а сверка `session id` не найдет настоящую сессию. Один и тот же путь, повторенный в двух файлах, неоднозначностью не считается (дедуп через `sort -u`);
- **профили читаются грепом, а не `source`** - исполнять профиль ради одного значения лишний риск;
- **`codex doctor` для этого не годится** - он печатает то же эффективное значение и промахивается одинаково с нами.

## Pre-check лимита перед запуском

Цель: не запускать `codex exec`, если окно `primary` на тарифе кончается. Какое это окно, решает тариф: у plus-плана `primary` - **5h** и рядом есть `secondary` (**1w**); на тарифе с одним окном `primary` - недельное, а `secondary` в rollout отсутствует вовсе (наблюдалось 04.09.2026). Метка в сообщениях берется из `window_minutes`, а не пишется руками. Иначе мы тратим минуты ожидания и контекст на ответ, который codex оборвет на полпути по rate-limit.

Источник данных - тот же rollout-лог, что и в "Статистика лимитов после сессии". Берем **последний** rollout-файл по mtime (не привязываемся к session id - его еще нет, мы только собираемся запускать новую сессию). В нем находим последний `token_count` event и из `rate_limits.primary` берем `used_percent` и `resets_at`.

Порог: **`left < 10%`** в окне `primary` - блокировать запуск. Цифра намеренно меньше порога "близко к лимиту" из саммари (20%): 10-20% хватит на средний аудит, а ниже 10% - уже почти гарантированно оборвет.

### Команда (запускать до `codex exec`)

Сначала - резолв домашней папки (см. "Резолв домашней папки codex"), в том же вызове: без него проверка молча выродится в "первый запуск".

```bash
if ! { [ -d "$CODEX_HOME/sessions" ] && [ -r "$CODEX_HOME/sessions" ] && [ -x "$CODEX_HOME/sessions" ]; }; then
  CODEX_HOME=$(grep -hs -E '^[[:space:]]*(export[[:space:]]+)?CODEX_HOME=' \
      ~/.bashrc ~/.bash_profile ~/.profile ~/.zshrc ~/.zshenv 2>/dev/null \
    | sed -E "s/.*CODEX_HOME=//; s/^[\"']//; s/[\"']$//; s|^~|$HOME|; s|\\\$HOME|$HOME|g; s|/+$||" \
    | while IFS= read -r d; do [ -n "$d" ] && [ -d "$d/sessions" ] && [ -r "$d/sessions" ] \
        && [ -x "$d/sessions" ] && echo "$d"; done | sort -u)
  [ -z "$CODEX_HOME" ] && [ -r "$HOME/.codex/sessions" ] && [ -x "$HOME/.codex/sessions" ] \
    && CODEX_HOME="$HOME/.codex"
  [ "$(printf '%s' "$CODEX_HOME" | grep -c .)" -gt 1 ] && CODEX_HOME=""
fi
{ [ -d "$CODEX_HOME/sessions" ] && [ -r "$CODEX_HOME/sessions" ] && [ -x "$CODEX_HOME/sessions" ]; } || CODEX_HOME=""

ROLLOUT=""
[ -n "$CODEX_HOME" ] && ROLLOUT=$(ls -1t "$CODEX_HOME"/sessions/*/*/*/rollout-*.jsonl 2>/dev/null | head -1)
if [ -z "$CODEX_HOME" ]; then
  echo "PRE_CHECK: codex home not resolved - LIMIT NOT CHECKED, ask the user before running"
elif [ -z "$ROLLOUT" ]; then
  echo "PRE_CHECK: no rollout logs found - first codex session on this machine, proceed with warning"
else
  python3 - "$ROLLOUT" <<'PY'
import json, sys, datetime

def fmt_until(s):
    s = int(s)
    if s < 60: return f"{s} sec"
    days  = s // 86400
    rem   = s - days * 86400
    hours = rem // 3600
    mins  = (rem - hours * 3600) // 60
    if days:  return f"{days} day {hours}:{mins:02d} hour"
    if hours: return f"{hours}:{mins:02d} hour"
    return f"{mins} min"

last = None
for line in open(sys.argv[1]):
    try:
        o = json.loads(line)
        p = o.get('payload') or {}
        if p.get('type') == 'token_count' and (p.get('rate_limits') or {}).get('primary'):
            last = p
    except Exception:
        pass
if not last:
    print("PRE_CHECK: no rate_limits event in latest rollout - proceed with warning")
    sys.exit(0)
pr = last['rate_limits'].get('primary') or {}
used = pr.get('used_percent', 0)
left = max(0.0, 100.0 - used)
rt = pr.get('resets_at')
if isinstance(rt, str):
    try:
        rt = datetime.datetime.fromisoformat(rt.replace('Z','+00:00')).timestamp()
    except Exception:
        rt = None
now = datetime.datetime.now(datetime.timezone.utc).timestamp()
reset = fmt_until(max(0, rt - now)) if rt else "unknown"
wm = pr.get('window_minutes') or 0
label = f"{wm//10080}w" if wm and wm % 10080 == 0 else f"{wm//60}h" if wm and wm % 60 == 0 else f"{wm}min"
if left < 10:
    print(f"PRE_CHECK: BLOCKED - {label} window left {left:.0f}% (used {used:.0f}%), reset {reset}")
else:
    print(f"PRE_CHECK: ok - {label} window left {left:.0f}% (used {used:.0f}%), reset {reset}")
PY
fi
```

### Как обрабатывать результат

- `PRE_CHECK: ok ...` - продолжить, запустить codex.
- `PRE_CHECK: BLOCKED ...` - НЕ запускать codex. Вернуть пользователю строку как есть + спросить: "ждать сброса или запустить все равно (рискуя оборваться)?". Не самочинно ставить таймер - решение пользователя. Если пользователь явно скажет "запускай" - запустить, иначе ждать.
- `PRE_CHECK: no rollout ...` / `no rate_limits event ...` - предупредить пользователя ("данных о лимите нет, запускаю") и идти дальше.

### Почему только окно `primary`

На тарифе с двумя окнами длинное (`1w` / `secondary`) тоже отдается в rollout, но один аудит за пару минут не сожжет неделю - там обычно остаток с большим запасом. Блокировать по `secondary` смысла нет: пользователю придется ждать дни до сброса. Если когда-нибудь окажемся в ситуации `secondary < 10%` - вынести отдельно в саммари как warning, но не блокировать запуск.

Лимит общий на все модели и все проекты сразу и тратится быстро при нескольких параллельных прогонах (наблюдалось: недельное окно съедено за 3 дня при 5-8 параллельных прогонах) - бюджет планировать по остатку окна, а не по одной задаче. По той же причине объем задания стоит прикинуть до старта (число единиц работы, не только "фича маленькая/большая"): позднее обнаружение реального объема ломает уже сделанный расчет квоты.

## Статистика лимитов после сессии

Codex CLI пишет лог сессии в `<домашняя папка codex>/sessions/<YYYY>/<MM>/<DD>/rollout-<ts>-<session_id>.jsonl` (папку резолвить сниппетом из "Резолв домашней папки codex", не собирать путь вручную). Внутри в событиях `token_count` есть блок `rate_limits` с двумя окнами:

- `primary` - короткое окно (для plus-плана `window_minutes=300`, то есть **5h**); на тарифе с одним окном оно единственное и недельное (`10080`).
- `secondary` - длинное окно (для plus-плана `window_minutes=10080`, то есть **1w**); на тарифе с одним окном отсутствует - сниппет печатает только те окна, что есть.

Каждое окно содержит `used_percent` (сколько уже потрачено) и `resets_at` (unix-timestamp, когда счетчик обнулится).

Сами токены (input/cached/output/reasoning) намеренно НЕ собираются - в этом скилле важно одно: насколько мы близко к ограничениям тарифа и когда они сбросятся.

> **Почему не через `/status`.** Слаш-команда `/status` в TUI Codex показывает лимиты, но через `codex exec "/status"` она не работает - модель воспринимает строку как обычный prompt и отвечает текстом про workdir/sandbox без чисел. Отдельного `codex status` subcommand-а тоже нет (`codex --help` не содержит). Поэтому стат собирается из локального rollout-лога, который codex пишет автоматически.

### Команда (запускать сразу после codex exec)

`<report-file>` - отчет твоего режима из таблицы "Режимы" (`audit-report-<topic>` / `consult-report-<topic>` / `escalate-report-<topic>`). Извлечь `session id` из шапки только что созданного отчета, найти соответствующий rollout-файл, достать последний `rate_limits` и дописать блок в конец отчета:

```bash
if ! { [ -d "$CODEX_HOME/sessions" ] && [ -r "$CODEX_HOME/sessions" ] && [ -x "$CODEX_HOME/sessions" ]; }; then
  CODEX_HOME=$(grep -hs -E '^[[:space:]]*(export[[:space:]]+)?CODEX_HOME=' \
      ~/.bashrc ~/.bash_profile ~/.profile ~/.zshrc ~/.zshenv 2>/dev/null \
    | sed -E "s/.*CODEX_HOME=//; s/^[\"']//; s/[\"']$//; s|^~|$HOME|; s|\\\$HOME|$HOME|g; s|/+$||" \
    | while IFS= read -r d; do [ -n "$d" ] && [ -d "$d/sessions" ] && [ -r "$d/sessions" ] \
        && [ -x "$d/sessions" ] && echo "$d"; done | sort -u)
  [ -z "$CODEX_HOME" ] && [ -r "$HOME/.codex/sessions" ] && [ -x "$HOME/.codex/sessions" ] \
    && CODEX_HOME="$HOME/.codex"
  [ "$(printf '%s' "$CODEX_HOME" | grep -c .)" -gt 1 ] && CODEX_HOME=""
fi
{ [ -d "$CODEX_HOME/sessions" ] && [ -r "$CODEX_HOME/sessions" ] && [ -x "$CODEX_HOME/sessions" ]; } || CODEX_HOME=""

SESSION_ID=$(awk -F': ' '/^session id:/ {print $2; exit}' docs/<report-file>.md)
if [ -z "$SESSION_ID" ] || [ -z "$CODEX_HOME" ]; then
  ROLLOUT=""
else
  ROLLOUT=$(ls -1t "$CODEX_HOME"/sessions/*/*/*/rollout-*-${SESSION_ID}.jsonl 2>/dev/null | head -1)
fi

{
  echo
  echo "## Rate limits"
  echo
  if [ -z "$ROLLOUT" ]; then
    if [ -z "$SESSION_ID" ]; then
      echo "no session id in report header - rate limits not collected"
    elif [ -z "$CODEX_HOME" ]; then
      echo "codex home not resolved - rate limits not collected (session id is fine)"
    else
      echo "no rollout file for session $SESSION_ID - rate limits not collected"
    fi
  else
  python3 - "$ROLLOUT" <<'PY'
import json, sys, datetime

def fmt_window(m):
    if m % 10080 == 0: return f"{m//10080}w"
    if m % 1440 == 0:  return f"{m//1440}d"
    if m % 60 == 0:    return f"{m//60}h"
    return f"{m}min"

def fmt_until(s):
    s = int(s)
    if s < 60: return f"{s} sec"
    days  = s // 86400
    rem   = s - days * 86400
    hours = rem // 3600
    mins  = (rem - hours * 3600) // 60
    if days:  return f"{days} day {hours}:{mins:02d} hour"
    if hours: return f"{hours}:{mins:02d} hour"
    return f"{mins} min"

last = None
with open(sys.argv[1]) as f:
    for line in f:
        try:
            o = json.loads(line)
            p = o.get('payload') or {}
            if p.get('type') == 'token_count' and (p.get('rate_limits') or {}).get('primary'):
                last = p
        except Exception:
            pass
if not last:
    print("no rate_limits event found"); sys.exit(0)
import re
with open(sys.argv[1]) as f:
    m = re.search(r'"model"\s*:\s*"([^"]+)"', f.read())
print(f"model: {m.group(1) if m else 'не найдена в rollout'}")
rl = last['rate_limits']
now = datetime.datetime.now(datetime.timezone.utc).timestamp()
for key in ('primary', 'secondary'):
    w = rl.get(key) or {}
    if not w: continue
    label = fmt_window(w.get('window_minutes', 0))
    used  = w.get('used_percent', 0)
    left  = max(0.0, 100.0 - used)
    rt    = w.get('resets_at')
    if isinstance(rt, str):
        try:
            rt = datetime.datetime.fromisoformat(rt.replace('Z','+00:00')).timestamp()
        except Exception:
            rt = None
    if rt:
        secs = max(0, rt - now)
        print(f"{label:>4}: left {left:.0f}% (used {used:.0f}%), reset {fmt_until(secs)}")
    else:
        print(f"{label:>4}: left {left:.0f}% (used {used:.0f}%)")
PY
  fi
} >> docs/<report-file>.md
```

Пример того, что получается в конце отчета:

```text
## Rate limits

  5h: left 98% (used 2%), reset 2:35 hour
  1w: left 99% (used 1%), reset 1 day 2:35 hour
```

### Как читать

- `left` - сколько процента квоты тарифа доступно сейчас. Если `< 20%` (для любого из окон, что есть) - вынести в саммари отдельной строкой "близко к лимиту по окну <метка>".
- `reset [D day ]H:MM hour` - компактный таймер до конца текущего окна: целые сутки (если есть), затем часы:минуты с ведущим нулем у минут. Примеры: `2:35 hour`, `1 day 0:00 hour`, `7 day 4:12 hour`, `45 min` (когда меньше часа), `30 sec` (когда меньше минуты). Окна скользящие: `resets_at` в исходных данных - это конец **текущего** окна, не "ровно через 5 часов от сейчас".
- Окна берутся из `window_minutes`. Лейблы `5h` / `1w` подставляются автоматически (300min -> `5h`, 10080min -> `1w`); если codex когда-нибудь сменит тариф/окна - в отчете появятся новые лейблы автоматически, скилл переписывать не надо.

### Если статистика не нашлась

- Файл rollout может не появиться, если codex упал на старте (например, проблемы с авторизацией) или сессия запущена с `--ephemeral`. В этом случае команда напечатает `no rollout file for session <id>` либо (если в шапке отчета нет `session id:`) `no session id in report header`. Это безопаснее, чем подцеплять чужой rollout - статистики просто не будет, и пользователь увидит причину.
- Не использовать `--ephemeral` в этом скилле - теряется возможность собрать статистику.
- Если в шапке отчета нет строки `session id:` - проверь сам план: по чеклисту "Упаковка контекста в план" Codex должен первой строкой отчета писать `session id: <id>`. Без этой строки пост-сборка статистики не работает.

## Ограничения

- Codex не получает контекст текущего чата, плана Claude Code, открытых файлов - только то, что в плане и что прочитает сам в репо.
- **`401 Missing bearer` при работающем интерактивном codex - это не протухший логин, а неэкспортированный `CODEX_HOME`**: `codex exec` - дочерний из скилла или запущенный руками из неинтерактивного shell - не увидел переменную из профиля пользователя и ушел в дефолт `~/.codex` без авторизации. Формы одного симптома: отчет из строк `ERROR: unexpected status 401 Unauthorized` + `Reconnecting...` при rc=0, пост-сбор пишет "no rollout file for session" (сессии и не было), `codex login status` отвечает `Not logged in`, `resume` настоящего id - `no rollout found for thread id`. Проверка в две строки: `codex login status` с переменной и без - разные ответы (`Logged in using ChatGPT` против `Not logged in`). Чинится экспортом в том же Bash-вызове (см. "Шаблон вызова"), а не просьбой перелогиниться (правило 9); `bash -lic` с профилем пользователя для этой проверки не брать - профили читаются грепом, а не исполняются (блок резолва). Наблюдалось 08.09.2026: агент предложил пользователю `codex login`, опроверг сам пользователь скриншотом работающего TUI.
- **В не-git проекте codex не стартует, причем молча.** Если `<project-root>` не git-репозиторий и не в trusted-каталогах codex, запуск без `--skip-git-repo-check` завершается с **rc=0** и отчетом ровно из двух строк:

  ```text
  Reading additional input from stdin...
  Not inside a trusted directory and --skip-git-repo-check was not specified.
  ```

  Опасность в том, что это неотличимо от "модель отработала и ничего не нашла": код возврата нулевой, файл отчета создан. Такой отчет за результат не принимать - перезапустить с флагом (правило 10). Статистику лимитов собирать бессмысленно: сессии не было, строки `session id` в отчете нет. Проверено на codex v0.144.x, поймано на vault-проекте без git.
- Read-only означает: codex не правит файлы, не коммитит, не запускает разрушительные команды. Файлы-секреты перекрывает скрипт по шаблонам имен из спеки. Не добавлять в план секреты - они попадут в `<plan-file>.md` (рабочая заметка, в git не едет, но локально лежит).
- Разрешение в `settings.local.json` строго `Bash(python3 <toolkit>/scripts/codex-sandbox.py audit:*)` - без этого префикса вызов потребует подтверждения.
- Скилл намеренно использует синхронный `codex exec`, а не async-плагин `codex-plugin-cc` (`codex-companion.mjs`). Async дает non-blocking, но он Linux-only (нужны `/proc`, `flock`, GNU coreutils) и тащит классы зависаний (зомби-джобы, заклинивший per-cwd broker - в цепочке worker->broker->app-server нет таймаута на запрос), для лечения которых нужна отдельная обвязка. У синхронного `codex exec` этих проблем нет: один foreground-процесс, Bash-таймаут чисто его снимает, чистить нечего. Non-blocking при необходимости берется отвязанным процессом ("Отвязанный запуск - протокол"); `run_in_background` для `codex exec` не используется - харнесс снимает долгие фоновые задачи.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.