Модуль 2 · CI/CD · урок 5 из 14
Codex в GitLab CI
Код агентства живёт в self-managed GitLab, поэтому основной сценарий автоматизации для нас — job в .gitlab-ci.yml. Готового компонента для GitLab CI, как GitHub Action, нет (встроенная интеграция с GitLab для ревью MR есть, но работает в облаке Codex — о ней в уроке про ревью). Это даже хорошо: всё делается обычным codex exec, и каждую деталь видно. В уроке соберём job, который проверяет merge request и показывает замечания в виджете Code Quality, и разберём, как не раздать при этом ключи всем подряд.
Из чего состоит схема
- Раннер. Docker-раннер, которому разрешён выход к API OpenAI. Удобно завести для Codex отдельный раннер с тегом
codex: проще следить за нагрузкой и сетевыми правилами. - Образ. Любой образ с Node.js 18+ для установки CLI через npm. Если проверка должна запускать тесты, берите образ проекта и доустанавливайте Codex в него.
- Ключ. API-ключ OpenAI в переменной CI/CD. Для автоматизации документация рекомендует именно API-ключ, а не вход через аккаунт ChatGPT.
- Файлы в репозитории. Промпт и JSON Schema лежат рядом с кодом в
.gitlab/codex/и меняются через MR. - Результат. Отчёт в формате GitLab Code Quality (CodeClimate JSON) и журнал событий — в артефактах.
Если раннер ходит в интернет через корпоративный прокси с подменой TLS-сертификатов, укажите путь к корневому сертификату в переменной CODEX_CA_CERTIFICATE (запасной вариант — SSL_CERT_FILE).
Ключ: переменные CI/CD
Создайте отдельный API-ключ для CI — не личный ключ разработчика. Тогда его можно ограничить, отслеживать расход и отозвать, не задев никого.
В GitLab: Settings → CI/CD → Variables → Add variable.
- Key:
OPENAI_API_KEY. - Masked — чтобы значение не попало в лог job.
- Environment scope:
codex. По умолчанию переменная доступна всем job пайплайна: линтеру, тестам, сборке фронта. Если в любом из них окажется скомпрометированная зависимость, она прочитает ключ. С областью окружения переменную получит только job, где указаноenvironment: codex. Сама документация GitLab рекомендует ограничивать так чувствительные переменные. - Учтите границу этой защиты. Область окружения спасает от скомпрометированной зависимости в соседнем job, но не от того, кто может пушить в репозиторий: в своей ветке он может поменять
.gitlab-ci.ymlи указать нужное окружение в любом job. Codex с ключом в CI — для проектов, где право push есть только у вашей команды. - Protected — осторожно. Защищённая переменная доступна только в пайплайнах защищённых веток и тегов. Пайплайн MR из обычной feature-ветки её не увидит, и job молча останется без ключа.
Внутри job ключ передаётся только вызову Codex: CODEX_API_KEY="$OPENAI_API_KEY" codex exec .... Так рекомендует документация Codex: не выставлять ключ на уровне всего job, где выполняется код из репозитория.
Для Enterprise есть ещё вариант с токеном доступа Codex (CODEX_ACCESS_TOKEN) для доверенной автоматизации — см. Access tokens.
Песочница в контейнере
Для проверки MR агенту достаточно читать файлы, поэтому запускаем с --sandbox read-only. Но в Docker есть нюанс. На Linux песочница Codex построена на bubblewrap и seccomp, и документация предупреждает: в контейнере она может не заработать, если конфигурация хоста или контейнера запрещает нужные операции с пространствами имён. Тогда job упадёт с ошибкой создания песочницы.
У вас два пути:
- Поставить в образ пакет
bubblewrap(в примере ниже он ставится вbefore_script) и настроить раннер так, чтобы контейнеру были доступны нужные операции с пространствами имён. Второе — вопрос к администратору раннера. - Сделать границей безопасности сам контейнер и запускать Codex с
--sandbox danger-full-accessвнутри него. Это документированный вариант, но тогда агент внутри контейнера может всё: писать файлы и ходить в сеть. Значит, в контейнере не должно быть ничего лишнего — ни чужих секретов, ни токенов с правом записи.
В обоих случаях добавьте две настройки окружения для команд, которые запускает агент:
-c shell_environment_policy.inherit=core
-c shell_environment_policy.ignore_default_excludes=falseПервая передаёт командам агента только базовый набор переменных окружения. Вторая включает автоматическое исключение переменных с KEY, SECRET и TOKEN в имени. По умолчанию это исключение выключено, а в окружении GitLab job всегда есть как минимум CI_JOB_TOKEN.
Рабочий пример
Три файла. Промпт:
Ты проверяешь merge request в проекте на OctoberCMS (PHP, Twig, JS).
Изменения: git diff <базовый коммит>...HEAD, базовый коммит указан в конце.
Ищи только существенные проблемы в изменённых строках:
- ошибки логики и необработанные исключения;
- SQL-инъекции, вывод через |raw в Twig без экранирования, отсутствие валидации входных данных;
- запросы к БД в циклах и выборки без ограничений;
- нарушения правил из раздела «Code Review Rules» в AGENTS.md.
Не сообщай о стиле и форматировании — это проверяет линтер.
Если не уверен в замечании — не включай его. Ничего не меняй в файлах.
path — путь от корня репозитория, lines.begin — номер строки в новой версии файла.
fingerprint — стабильная строка из пути, имени проверки и сути проблемы.
Если замечаний нет, верни пустой массив issues.Схема ответа. GitLab ждёт массив, а корень ответа по схеме должен быть объектом, поэтому массив лежит в поле issues:
{
"type": "object",
"properties": {
"issues": {
"type": "array",
"items": {
"type": "object",
"properties": {
"description": { "type": "string" },
"check_name": { "type": "string" },
"fingerprint": { "type": "string" },
"severity": { "type": "string", "enum": ["info", "minor", "major", "critical", "blocker"] },
"location": {
"type": "object",
"properties": {
"path": { "type": "string" },
"lines": {
"type": "object",
"properties": { "begin": { "type": "integer" } },
"required": ["begin"],
"additionalProperties": false
}
},
"required": ["path", "lines"],
"additionalProperties": false
}
},
"required": ["description", "check_name", "fingerprint", "severity", "location"],
"additionalProperties": false
}
}
},
"required": ["issues"],
"additionalProperties": false
}И сам job:
stages:
- test
- review
codex_quality:
stage: review
image: node:22-bookworm
tags: [codex]
environment:
name: codex
action: prepare
variables:
CODEX_VERSION: "0.163.0"
GIT_DEPTH: "0"
rules:
- if: '$CI_PIPELINE_SOURCE != "merge_request_event"'
when: never
- if: '$CI_MERGE_REQUEST_SOURCE_PROJECT_ID != $CI_PROJECT_ID'
when: never
- changes:
- "**/*.{php,htm,js,twig}"
interruptible: true
timeout: 20 minutes
allow_failure: true
before_script:
- apt-get update && apt-get install -y --no-install-recommends jq bubblewrap
- npm install -g "@openai/codex@${CODEX_VERSION}"
- codex --version
script:
- set -euo pipefail
- mkdir -p codex-out
- |
{
cat .gitlab/codex/quality-prompt.md
echo
echo "Базовый коммит: ${CI_MERGE_REQUEST_DIFF_BASE_SHA}"
} | CODEX_API_KEY="$OPENAI_API_KEY" codex exec \
--ephemeral \
--sandbox read-only \
-c shell_environment_policy.inherit=core \
-c shell_environment_policy.ignore_default_excludes=false \
--output-schema .gitlab/codex/quality.schema.json \
-o codex-out/result.json \
--json - > codex-out/events.jsonl
- jq -e '.issues | type == "array"' codex-out/result.json > /dev/null
- jq '.issues' codex-out/result.json > gl-code-quality-report.json
- echo "Замечаний: $(jq length gl-code-quality-report.json)"
artifacts:
when: always
expire_in: 14 days
paths:
- codex-out/
reports:
codequality: gl-code-quality-report.jsonРазбор по строкам
rules. Job идёт только в пайплайнах merge request, только для MR из этого же проекта и только если изменились файлы кода. Форки отсекаются: в них нельзя доверять ни коду, ни тексту MR. Правило про изменения экономит запуски на правках документации. Не ставьте в rules условие на наличие OPENAI_API_KEY: GitLab предупреждает, что переменные с областью окружения могут быть не определены в момент вычисления правил.
environment: codex с action: prepare. Именно это даёт job доступ к переменной с областью codex. prepare означает, что job не считается деплоем и не создаёт записей о развёртывании.
CODEX_VERSION. Версия CLI закреплена. Флаги и поведение меняются между релизами, и пайплайн не должен ломаться оттого, что вчера вышла новая версия. Обновляйте её осознанно, отдельным MR. Последний стабильный релиз на 22 октября 2026 года — 0.163.0.
GIT_DEPTH: "0". Полная история нужна, чтобы базовый коммит MR был в клоне и git diff сработал. Для больших репозиториев можно подобрать глубину поменьше.
--ephemeral. Сессия не сохраняется: продолжать её в CI незачем, а лишние файлы в контейнере ни к чему.
set -euo pipefail и jq -e. Если Codex упал или вернул что-то не то, job падает, а не публикует пустой отчёт. Упавший запуск — не «замечаний нет».
allow_failure: true. Пока вы не откалибровали промпт, проверка не должна блокировать мерж. Когда доля полезных замечаний вас устроит, флаг можно убрать.
interruptible и timeout. Новый пуш в MR отменит устаревший запуск, а зависший агент не займёт раннер на час.
artifacts. Отчёт codequality GitLab покажет в виджете MR — это работает во всех редакциях, включая Free. Журнал events.jsonl остаётся для разбора: какие команды запускал агент и сколько токенов ушло.
Ограничения прав: чек-лист
- Ключ — отдельный для CI, с областью окружения, Masked, с лимитами и регулярной ротацией.
--sandbox read-onlyдля анализа. Права на запись — только job, которым они действительно нужны (см. автопочинку CI).- Если песочница в контейнере не работает и вы перешли на
danger-full-access, в job не должно быть токенов с правом записи в GitLab. - Команды агента получают урезанное окружение (
shell_environment_policy). - Форки и MR от внешних участников не запускают Codex.
- Публикация результатов в MR — отдельным job со своим токеном. Как это сделать — в уроке про ревью merge request.
Учтите и то, что сам процесс codex держит ключ в своём окружении, а агент работает от того же пользователя. Считайте, что ключ потенциально доступен агенту, и стройте защиту так, чтобы утечка не стала катастрофой: отдельный ключ, ограничения, мониторинг расхода. Подробнее — в уроке «Безопасность автоматизации».
Похожий пример есть в OpenAI Cookbook: там же разобран разбор отчёта SAST. Учтите, что примеры Cookbook используют устаревший флаг --full-auto и разбирают ответ по текстовым маркерам. --output-schema надёжнее.
Попробуйте сами
10–15 мин на рабочем месте- Заведите переменную
OPENAI_API_KEYс областью окруженияcodexв тестовом проекте. Добавьте job из урока и откройте MR с заведомо проблемным кодом — например, с выводом пользовательского ввода через|raw. - Посмотрите, появилось ли замечание в виджете Code Quality, и откройте
codex-out/events.jsonlиз артефактов: какие команды выполнял агент? - Создайте второй job без
environment: codexи выведите в нёмecho "${OPENAI_API_KEY:-нет ключа}". Убедитесь, что ключа там нет.
Коротко
- Codex в GitLab CI — это
codex execв обычном job. Промпт и схема лежат в репозитории. - Ключ: отдельный, Masked, с областью окружения
codex. В job он передаётся только вызову Codex. - Песочница в Docker может не заработать. Тогда граница безопасности — контейнер, и в нём не должно быть лишних секретов.
- Версия CLI закреплена, упавший запуск роняет job, отчёт Code Quality виден в MR.
- Форки не запускают Codex. Публикация результатов идёт отдельным job.