Amiga
Оглавление курса · урок 7 из 14

Модуль 2 · CI/CD · урок 7 из 14

Автопочинка упавшего CI

Упавший пайплайн — самая понятная задача для агента: есть воспроизводимая ошибка, есть команда проверки, есть критерий готовности. В документации Codex для этого описан готовый паттерн для GitHub Actions. В уроке разберём, на чём он держится, и перенесём его в GitLab CI с одним важным дополнением — решение о создании MR принимает человек.

Паттерн из документации

Схема OpenAI для GitHub состоит из шести шагов:

  1. Отдельный workflow запускается, когда основной CI завершился с ошибкой.
  2. Код упавшего коммита забирается с правами только на чтение.
  3. Зависимости ставятся до запуска Codex, и ключ OpenAI этим шагам не виден.
  4. Запускается Codex через openai/codex-action.
  5. Изменения Codex сохраняются как патч-артефакт.
  6. Отдельный job с правами на запись применяет патч и открывает PR. Ключа OpenAI у него нет.

Полный workflow есть в документации. Ключевая идея в разделении: агент не может ничего записать в репозиторий, а то, что может писать, не запускает агента. Ключевой фрагмент job с Codex выглядит так:

yaml
- 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.
yaml · .gitlab-ci.yml
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 мин на рабочем месте
  1. Возьмите проект с PHPUnit или Jest и сломайте один тест намеренно: поменяйте ожидаемое значение в коде, а не в тесте. Запустите локально тот же промпт через codex exec --sandbox workspace-write и посмотрите на дифф.
  2. Добавьте в пайплайн тестового проекта codex_autofix и codex_open_mr. Убедитесь, что патч лежит в артефактах, а MR создаётся только по кнопке.
  3. Попросите Codex в промпте «поправить тест, если так проще» и проверьте, что механическая проверка отклонит такой патч.

Коротко

  • Паттерн: агент правит рабочую копию без права push, патч — артефакт, MR создаёт отдельный job.
  • В GitLab: when: on_failure для Codex и ручной job с отдельным токеном для MR.
  • Исключите ветки автопочинки из rules, иначе получите цикл.
  • Проверяйте патч механически: запрет на tests/, CI, инструкции агента и зависимости, ограничение размера.
  • Автопочинка — для детерминированных падений кода, не для нестабильных тестов и инфраструктуры.
Отметка хранится только в вашем браузере
Хотите связаться с владельцем
компании напрямую?
Дмитрий Тарасов
Дмитрий Тарасов
СЕО

НАПИСАТЬ

Написать в MAX