openai codex · headless · вызов из claude code

Codex exec: свои инструкции и правила

Неинтерактивный запуск Codex работает по той же подписке ChatGPT и встраивается в любой скрипт. Малоизвестная часть: ему можно передавать собственные инструкции и правила выполнения.

01 · база

У Codex есть запуск без интерфейса

Интерактивный codex видели все: TUI, диалог, подтверждения команд. Второй способ запуска заметен хуже — он живёт в --help строчкой ниже:

выдержка · codex --help · v0.144.0
exec        Run Codex non-interactively [aliases: e]
mcp-server  Start Codex as an MCP server (stdio)

Вторая строчка выдержки здесь не понадобится: mcp-server — это Codex в роли инструмента для других агентов. Нас интересует первая. codex exec "задача" — не урезанная версия, а тот же агент без интерфейса; он выполняет задачу до конца, печатает результат и завершается — как claude -p у Claude Code:

Оплата не меняется: codex login один раз, дальше токены обновляются сами.

Обычный процесс со stdout запускается откуда угодно: из CI, из cron, из git-hook, из другого агента. Одна проблема: в stdout попадает вся сессия подряд — заголовок, команды агента, финальный ответ. Скрипту из этого потока нужен только ответ, причём предсказуемой формы. Для этого у exec есть два флага:

выдержка · codex exec --help · v0.144.0
--output-schema <FILE>   Path to a JSON Schema file describing the
                         model's final response shape
-o, --output-last-message <FILE>
                         Specifies file where the last message from
                         the agent should be written
ответ отдельно от логов, форма — по вашей схеме
$ codex exec \
    --output-schema shape.json \   # какой формы должен быть ответ
    -o result.json \               # куда положить финальное сообщение
    "Найди все TODO старше года и сгруппируй по владельцам"

$ cat result.json               # это читает уже скрипт, не человек
{"todos": [{"owner": "pavel", "file": "src/auth.py", "line": 42}, …]}

-o отделяет ответ от логов, --output-schema гарантирует его структуру. Вместе они делают exec полноценным шагом пайплайна: следующей команде не приходится выуживать данные из свободного текста.

02 · кейс

Типичный кейс: второе мнение на ревью

Чаще всего headless Codex используют для независимого ревью: дифф проверяет не та модель, которая его писала. У меня для этого кастомный сабагент Claude Code — один markdown-файл в ~/.claude/agents/. Говорю «сделай ревью кодексом» — и дальше цепочка отрабатывает сама:

Весь сабагент — это описание на естественном языке:

~/.claude/agents/codex-reviewer.md
---
name: codex-reviewer
description: Independent code review by OpenAI Codex. Use when the user
  asks for a second opinion, says "let codex review this", wants an
  outside perspective on uncommitted work, a branch, or a commit.
tools: Bash, Read
---

You are a thin bridge between Claude Code and the OpenAI Codex CLI.
Invoke `codex review` correctly and return its output verbatim.
Do NOT add your own analysis, agree, disagree, or filter findings.

## Picking the review scope
- default / "uncommitted" / "current changes"  → --uncommitted
- "branch" / "vs main" / "before merging"      → --base main
- "this commit" / SHA mentioned                → --commit <SHA>
- custom focus ("check race conditions")       → append as prompt arg

## Running it
Bash timeout 600000 — review on big diffs takes minutes.

## Do not
- summarize or filter Codex's findings
- run `codex login` — surface auth errors to the user instead

Скоуп ревью выбирается из формулировки — незакоммиченное, ветка против main, конкретный коммит, — а свой фокус уходит позиционным аргументом. На этом типичное использование обычно и заканчивается: запустил, прочитал stdout.

03 · инструкции

Пять каналов своих инструкций

Строчка-задача — только нижний слой промпта. Перед ней exec молча подкладывает ещё два: инструкции проекта и рабочую политику — ровно так же, как это делает интерактивная сессия. Каждый слой управляется своим каналом:

1 · AGENTS.md — знания уровня проекта

Headless-запуск ищет инструкции в тех же местах, что интерактивный, и складывает их в одну цепочку:

какие файлы попадут в контекст при cwd = src/billing/
~/.codex/AGENTS.md             глобальные инструкции
<repo>/
├─ AGENTS.md                   корень проекта
└─ src/billing/               ← рабочая директория
   └─ AGENTS.md                самый близкий к cwd — главный:
                                 при конфликте перекрывает ① и ②

Суммарный лимит цепочки — ключ project_doc_max_bytes, по умолчанию 32 КиБ. Практическое следствие: секция в AGENTS.md управляет даже встроенным ревьюером — его рубрика уступает инструкциям проекта:

<repo>/AGENTS.md
## Review guidelines
- Any silent `except`/`catch` without logging is a P1 finding.
- We target Python 3.12+; do not flag `match` statements.

Что канал работает в headless, проверяется за минуту — маркер в инструкциях, ответ из инструкций:

shell
$ mkdir demo && cd demo && git init -q
$ echo 'If asked for the magic word, reply exactly: BANANA-42.' > AGENTS.md
$ codex exec "What is the magic word?"
BANANA-42

2 · Промпт: аргумент + stdin — задача и данные раздельно

выдержка · codex exec --help · v0.144.0
[PROMPT]  Initial instructions for the agent. If not provided as an
          argument (or if `-` is used), instructions are read from stdin.
          If stdin is piped and a prompt is also provided, stdin is
          appended as a <stdin> block

Последнее предложение — самое полезное: аргумент и stdin не конкурируют, а склеиваются. Формулировка задачи остаётся стабильной, данные к ней — потоковые:

shell
$ tail -200 error.log | codex exec \
    "Сопоставь стектрейсы из <stdin> с кодом в src/ и назови виновный модуль"
что получит модель в слое «задача»
Сопоставь стектрейсы из <stdin> с кодом в src/ …      ← аргумент
<stdin>
Traceback (most recent call last): …                  ← 200 строк лога
</stdin>

3 · developer_instructions — политика отдельно от задачи

Ключ config.toml; как и любой ключ, переопределяется на один запуск флагом -c:

выдержка · codex exec --help · v0.144.0
-c, --config <key=value>  Override a configuration value that would
        otherwise be loaded from ~/.codex/config.toml. Use a dotted
        path (foo.bar.baz) to override nested values.

Текст попадает в developer-слот — выше user-промпта (см. схему в начале раздела). При конфликте побеждает верхний слой, и на этом строится приём: задача формулируется один раз, а политику скрипт меняет по окружению:

ночной job: одна задача, разные среды
# staging — политики нет, «исправь» значит исправь:
$ codex exec "Разбери новые предупреждения компилятора и исправь их"

# production — та же задача, но сверху политика read-only:
$ codex exec \
    -c developer_instructions="You run unattended in CI. Never modify files; propose every change as a unified diff." \
    "Разбери новые предупреждения компилятора и исправь их"
конфликт слоёв в production-прогоне
developer   «Never modify files; propose a unified diff»   ← сильнее
user        «…и исправь их»                                 ← слабее
───────────────────────────────────────────────────────
исход       файлы не тронуты; «исправь» = готовый diff в ответе

Наложить полученный diff (git apply) — уже осознанное действие человека, а не самодеятельность ночного агента.

4 · project_doc_fallback_filenames — один файл инструкций на все агенты

Ключ задаёт, какие имена искать в директории, если AGENTS.md там нет (по умолчанию — никаких). Типичная ситуация: в репозитории уже есть CLAUDE.md для Claude Code, и дублировать его в AGENTS.md не хочется:

shell
$ codex exec -c 'project_doc_fallback_filenames=["CLAUDE.md"]' \
    "Найди мёртвый код и составь список кандидатов на удаление"
как решается каждая директория цепочки
есть AGENTS.md?      да → берём его
                     нет → ищем по списку fallback-имён по порядку:
                           ["CLAUDE.md", …] → нашли CLAUDE.md → берём его

Прописанный в config.toml (а не через -c), ключ действует на все запуски, включая интерактивные.

5 · Профили -p <name> — набор настроек одним флагом

выдержка · codex exec --help · v0.144.0
-p, --profile <CONFIG_PROFILE>  Layer $CODEX_HOME/<name>.config.toml
                                on top of the base user config

Слои конфига накладываются по порядку — каждый следующий перекрывает предыдущий:

слои конфига одного запуска
~/.codex/config.toml   →   review-strict.config.toml   →   -c key=value
базовый конфиг             профиль (флаг -p)                разовые оверрайды

Профиль — это модель, глубина reasoning и developer-инструкции разом. Держите fast для черновых прогонов и review-strict для предрелизных:

~/.codex/review-strict.config.toml
model_reasoning_effort = "xhigh"
developer_instructions = """
You are reviewing production code. Prioritize data loss,
race conditions, security. Ignore naming and formatting.
"""
shell
$ codex review -p review-strict --base main

Шестым каналом можно считать skills (~/.codex/skills/) — они инжектятся и в headless. А вот system-слот в exec не подменяется: параметр base-instructions существует только у MCP-инструмента.

04 · rules

Правила выполнения: execpolicy в headless

Инструкции — половина дела. Вторая половина — правила: в терминологии Codex «rules» — это не инструкции модели, а политика выполнения shell-команд, Starlark-файлы *.rules.

developers.openai.com/codex/rules «When you add a command to the allow list in the TUI, Codex writes to the user layer at ~/.codex/rules/default.rules»

То есть файл правил уже копится у каждого, кто жмёт «always allow» в интерактиве. Headless-запуски читают те же файлы — в codex exec --help это видно от противного: раз есть флаг отключения, по умолчанию правила загружены.

выдержка · codex exec --help · v0.144.0
--ignore-rules  Do not load user or project execpolicy `.rules`
                files for this run
откуда читаются правила
~/.codex/rules/*.rules          user-слой — всегда
<repo>/.codex/rules/*.rules     project-слой — только если проект trusted

Каждая команда агента сверяется с правилами. В headless по умолчанию approval = never — спросить некого, поэтому исходы жёстче, чем в TUI:

судьба shell-команды агента в headless
shell-команда агента
  │ совпала с prefix_rule?
  ├─ decision = allow      →  выполняется сразу, вне sandbox
  ├─ decision = prompt     →  блок: «approval required by policy,
  │                            but AskForApproval is set to Never»
  ├─ decision = forbidden  →  блок
  └─ правила нет           →  обычный режим sandbox
                              (read-only / workspace-write)

Так правила из «удобства одобрений» превращаются в границы для автономного агента: явные allow — узкий коридор разрешённого, forbidden — жёсткий запрет. Пример для CI-прогона, где Codex имеет право запускать только тесты и линтер:

~/.codex/rules/ci.rules
prefix_rule(pattern=["pytest", "-q"],        decision="allow")
prefix_rule(pattern=["npm", "run", "lint"],  decision="allow")
prefix_rule(pattern=["git", "push"],         decision="forbidden")
prefix_rule(pattern=["rm", "-rf"],           decision="forbidden")
проверка правила без запуска агента
$ codex execpolicy check --pretty \
    --rules ~/.codex/rules/ci.rules -- pytest -q
частая ошибка Правило с decision="prompt" в headless не «спросит потом» — команда просто не выполнится. Для пайплайнов правила должны быть явными allow либо forbidden.
05 · границы

Что доезжает до headless, а что нет

Инструкции и правила доезжают до headless полностью. Память — нет, и это не ошибка конфигурации:

КаналИнтерактивный TUIcodex exec
AGENTS.md+ fallback-имена да да
Rules*.rules / execpolicy да дас ~0.122; в 0.114 был баг (issue #14572)
Memories: чтение да да
Memories: записьпополнение памяти из сессии да нет
developers.openai.com/codex/memories «Local Codex memories are off by default»
почему headless не пополняет память
TUI- и IDE-сессии  ──▶  память  ──▶  инжект в новые сессии (и в exec тоже)
exec-сессии        ──✗  не источник: что агент узнал в headless-прогоне,
                        в память не попадает никогда

Память — экспериментальная фича (состояние покажет codex features list), и это ограничение — сознательное: сессии exec не входят в список «interactive session sources», допущенных к генерации воспоминаний. Документация об этом молчит; ограничение видно только в исходниках openai/codex. Вывод для практики: контекст, который должен доезжать до headless надёжно, живёт в AGENTS.md — не в памяти.

если «не работает» Часть мифов о headless — наследие старых версий: например, в 0.114 exec игнорировал rules. Прежде чем спорить в комментариях — codex --version.