Модуль 2 · CI/CD · урок 7 из 14
Автопочинка упавшего CI
Упавший пайплайн — самая понятная задача для агента: есть воспроизводимая ошибка, есть команда проверки, есть критерий готовности. В документации Codex для этого описан готовый паттерн для GitHub Actions. В уроке разберём, на чём он держится, и перенесём его в GitLab CI с одним важным дополнением — решение о создании MR принимает человек.
Паттерн из документации
Схема OpenAI для GitHub состоит из шести шагов:
- Отдельный workflow запускается, когда основной CI завершился с ошибкой.
- Код упавшего коммита забирается с правами только на чтение.
- Зависимости ставятся до запуска Codex, и ключ OpenAI этим шагам не виден.
- Запускается Codex через
openai/codex-action. - Изменения Codex сохраняются как патч-артефакт.
- Отдельный job с правами на запись применяет патч и открывает PR. Ключа OpenAI у него нет.
Полный workflow есть в документации. Ключевая идея в разделении: агент не может ничего записать в репозиторий, а то, что может писать, не запускает агента. Ключевой фрагмент job с Codex выглядит так:
- name: Run Codex
uses: openai/codex-action@v1
with:
openai-api-key: ${{ secrets.OPENAI_API_KEY }}
prompt: |
Run `npm test --silent` to reproduce the failure. Identify the minimal
change needed to make the tests pass, implement only that change, and
run `npm test --silent` again. Do not refactor unrelated files.
- name: Create patch artifact
run: |
git add -N .
git diff --binary HEAD > codex.patchПереносим в GitLab
В GitLab нет отдельного события «пайплайн упал», зато есть when: on_failure: job запускается, только если в предыдущих стадиях что-то упало. Схема из трёх job:
phpunit— обычные тесты. Лог сохраняется артефактом даже при падении.codex_autofix— запускается при падении, получает ключ через окружениеcodex, правит рабочую копию и сохраняет патч.codex_open_mr— ручной job. Человек смотрит патч в артефактах и решает, создавать ли MR. Токен с правом push лежит в отдельном окруженииcodex-publish.
stages: [test, autofix]
variables:
CODEX_VERSION: "0.163.0"
phpunit:
stage: test
image: $CI_REGISTRY_IMAGE/php-node:8.3
script:
- set -o pipefail
- composer install --no-interaction --no-progress
- vendor/bin/phpunit 2>&1 | tee phpunit.log
artifacts:
when: always
paths: [phpunit.log]
expire_in: 3 days
codex_autofix:
stage: autofix
image: $CI_REGISTRY_IMAGE/php-node:8.3
environment: { name: codex, action: prepare }
dependencies: [phpunit]
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_SOURCE_PROJECT_ID == $CI_PROJECT_ID && $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME !~ /^codex\/autofix-/'
when: on_failure
allow_failure: true
timeout: 30 minutes
script:
- set -euo pipefail
- npm install -g "@openai/codex@${CODEX_VERSION}"
- composer install --no-interaction --no-progress
- mkdir -p codex-out
- |
CODEX_API_KEY="$OPENAI_API_KEY" codex exec \
--ephemeral \
--sandbox workspace-write \
-c shell_environment_policy.inherit=core \
-c shell_environment_policy.ignore_default_excludes=false \
-o codex-out/summary.md \
"Тесты PHPUnit упали, лог в phpunit.log. Воспроизведи падение командой
vendor/bin/phpunit, найди минимальное исправление в коде приложения
и снова запусти тесты. Не меняй файлы в tests/, не отключай и не
пропускай тесты, не трогай файлы, не связанные с падением.
В конце коротко опиши причину и исправление." > /dev/null
- git add -N -- . ':!codex-out' ':!phpunit.log'
- git diff --binary -- . ':!codex-out' ':!phpunit.log' > codex-out/codex.patch
- test -s codex-out/codex.patch || { echo "Codex не предложил изменений"; exit 1; }
- |
if git diff --name-only | grep -qE '^(tests/|\.gitlab-ci\.yml|\.gitlab/|\.codex/|\.agents/|AGENTS\.md|composer\.(json|lock)|package(-lock)?\.json)'; then
echo "Патч меняет тесты, CI, инструкции агента или зависимости — отклонён"; exit 1
fi
- git diff --stat
artifacts:
when: always
paths: [codex-out/]
expire_in: 3 days
codex_open_mr:
stage: autofix
image: alpine:3.20
environment: { name: codex-publish, action: prepare }
needs:
- job: codex_autofix
artifacts: true
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event" && $CI_MERGE_REQUEST_SOURCE_PROJECT_ID == $CI_PROJECT_ID && $CI_MERGE_REQUEST_SOURCE_BRANCH_NAME !~ /^codex\/autofix-/'
when: manual
allow_failure: true
script:
- apk add --no-cache git
- git config user.name "Codex autofix"
- git config user.email "codex-bot@example.com"
- BRANCH="codex/autofix-${CI_PIPELINE_ID}"
- git switch -c "$BRANCH"
- git apply --index codex-out/codex.patch
- git commit -m "Автоисправление тестов для !${CI_MERGE_REQUEST_IID}"
- >
git push
-o merge_request.create
-o merge_request.target="$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME"
-o merge_request.title="Codex: исправление тестов для !${CI_MERGE_REQUEST_IID}"
"https://oauth2:${CODEX_BOT_TOKEN}@${CI_SERVER_HOST}/${CI_PROJECT_PATH}.git" "$BRANCH"Образ php-node здесь условный: нужен образ вашего проекта с PHP, Composer и Node.js для установки CLI. CODEX_BOT_TOKEN — токен доступа проекта (Project access token) с правом write_repository, переменная Masked с областью окружения codex-publish. Email бота подставьте свой.
Что здесь важно
MR создаётся в ветку автора, а не в main. Исправление предлагается как MR в исходную ветку упавшего MR. Автор видит его рядом со своей работой и сам решает, принять ли.
Человек нажимает кнопку. codex_open_mr — ручной job. Перед нажатием можно скачать codex-out/codex.patch и summary.md из артефактов и посмотреть, что предлагает агент. Если патч плохой, MR просто не создаётся. Поведение ручного job с needs в упавшем пайплайне проверьте на своей версии GitLab, прежде чем на него полагаться.
Защита от зацикливания. Ветки codex/autofix-* исключены в rules. Иначе упавшие тесты в MR с исправлением запустят новое исправление, и так по кругу.
Механические проверки патча. Агента просили не трогать тесты, но просьба — не гарантия. Скрипт отклоняет патч, который меняет tests/, а также конфигурацию CI (.gitlab-ci.yml, .gitlab/), инструкции и настройки агента (AGENTS.md, .codex/, .agents/) и манифесты зависимостей. Агент, прочитавший в логе внедрённую инструкцию, не должен через автопочинку переписать пайплайн или подложить зависимость. Аналогично можно ограничить размер: если git diff --stat показывает сотни строк, это уже не «минимальное исправление».
Права агента. Codex работает с --sandbox workspace-write: может править рабочую копию, но сеть выключена. Зависимости установлены до запуска агента, поэтому сеть ему не нужна. Если песочница в вашем Docker-раннере не запускается, вспомните разбор из урока про GitLab CI: переход на danger-full-access делает контейнер единственной границей, и тогда особенно важно, что токена на push в этом job нет.
Ключ и код проекта в одном job. Это слабое место схемы. composer install и тесты выполняют код из репозитория в том же job, где есть ключ OpenAI. Мы сузили риск: ключ передаётся только процессу Codex, а команды агента получают урезанное окружение. Но полной изоляции нет. Поэтому автопочинка — только для MR из своего проекта, от своей команды, с отдельным ключом CI.
Каким падениям подходит автопочинка
Хорошо работает:
- упавшие юнит-тесты после правки бизнес-логики;
- ошибки статического анализа и линтера, которые не чинятся автоматически;
- сломанные импорты и опечатки после рефакторинга.
Плохо работает и лучше не включать:
- нестабильные тесты: агент «починит» то, что не сломано (о них — урок «Нестабильные тесты»);
- падения из-за инфраструктуры: недоступный реестр, кончилось место, упал сервис;
- e2e-тесты, которые требуют поднятого окружения и браузера, если в job его нет.
Начните с одного вида проверок, например только PHPUnit, и посмотрите на первые десять патчей. Долю принятых исправлений и стоит считать главной метрикой.
Другие способы
Если репозиторий на GitHub и подключена интеграция Codex, комментарий @codex fix the CI failures в PR запускает облачную задачу, которая может запушить исправление в ветку. В GitLab то же самое требует настроенного окружения проекта (project environment), подробнее — в уроке про ревью. В десктопном приложении ChatGPT у плагина Code Review есть кнопка Fix для упавших проверок PR. Эти варианты проще, но дают меньше контроля над правами и промптом, чем свой job.
Попробуйте сами
10–15 мин на рабочем месте- Возьмите проект с PHPUnit или Jest и сломайте один тест намеренно: поменяйте ожидаемое значение в коде, а не в тесте. Запустите локально тот же промпт через
codex exec --sandbox workspace-writeи посмотрите на дифф. - Добавьте в пайплайн тестового проекта
codex_autofixиcodex_open_mr. Убедитесь, что патч лежит в артефактах, а MR создаётся только по кнопке. - Попросите Codex в промпте «поправить тест, если так проще» и проверьте, что механическая проверка отклонит такой патч.
Коротко
- Паттерн: агент правит рабочую копию без права push, патч — артефакт, MR создаёт отдельный job.
- В GitLab:
when: on_failureдля Codex и ручной job с отдельным токеном для MR. - Исключите ветки автопочинки из
rules, иначе получите цикл. - Проверяйте патч механически: запрет на
tests/, CI, инструкции агента и зависимости, ограничение размера. - Автопочинка — для детерминированных падений кода, не для нестабильных тестов и инфраструктуры.