2025, Dec 17 15:02

Trusted Publishing в GitHub Actions: 400 при публикации через reusable workflow — как исправить

Почему публикация в Test PyPI через Trusted Publishing и reusable workflow в GitHub Actions падает с 400, и как перевести деплой на рабочую схему без паролей.

Переход в конвейере GitHub Actions с загрузки по паролю на PyPI Trusted Publishing выглядит обманчиво простым: убираем параметр пароля, выдаём id-token: write — и наслаждаемся деплоями без токенов. На практике есть острый угол: кажется, что всё прошло, но публикация заканчивается непонятной ошибкой 400 от издателя. Сценарий ниже показывает, что происходит при попытке отправить development wheel на test.pypi.org/legacy с Trusted Publisher через переиспользуемый workflow, и почему это не срабатывает.

Где миграция дает сбой

Есть job, который собирает и отправляет development wheel в Test PyPI. Раньше всё проходило, потому что использовался пароль. После миграции аргумент пароля убрали, а разрешения id-token: write добавили. Запуск отрабатывает, но шаг публикации возвращает HTTP 400 без каких‑либо подробностей.

name: dev-wheel-publish
on:
  push:
    branches:
      - main

jobs:
  release_dev_build:
    permissions:
      id-token: write
      contents: read
    uses: ./.github/workflows/reusable-publish.yml
    with:
      target-repo: https://test.pypi.org/legacy/

В такой схеме фактическая загрузка выполняется внутри переиспользуемого workflow, вызываемого через uses. Пароль убран, id-token: write на месте — всё выглядит в духе Trusted Publishing. Но на выходе всё равно 400.

Почему это не работает

Корневая причина прямо указана PyPI. На данный момент Trusted Publisher не поддерживается при использовании через переиспользуемый workflow. Это практическое ограничение и именно оно приводит к «тихому» сбою при публикации.

Переиспользуемые workflows пока нельзя использовать в качестве workflow для Trusted Publisher. Это практическое ограничение; его статус отслеживается в warehouse#11096.

Иными словами, даже если Trusted Publisher настроен и в workflow есть id-token: write, поток OIDC не сработает, если его пропускать через переиспользуемый workflow. Отсюда и загадочная 400 на шаге публикации.

Как переработать workflow, чтобы он заработал с Trusted Publishing

Решение — вынести логику публикации из переиспользуемого workflow в обычный (непереиспользуемый) workflow в репозитории, настроенном как Trusted Publisher. Оставьте на job разрешение id-token: write и полностью откажитесь от параметра пароля.

name: dev-wheel-publish
on:
  push:
    branches:
      - main

jobs:
  dev_whl_publish:
    permissions:
      id-token: write
      contents: read
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Build wheel
        run: |
          echo "build steps here"

      - name: Publish wheel to Test PyPI
        run: |
          echo "upload to https://test.pypi.org/legacy/"

Такая структура оставляет задачу публикации в полноценном workflow, а не в переиспользуемом. Ключевые элементы те же, что и при попытке миграции: ввод пароля исчез, а id-token: write присутствует.

Ещё один практический момент, всплывший при проверке: без permissions: id-token: write на job или всём workflow Trusted Publishing не заработает. В примере выше это разрешение явно задано. Если конвейер всё равно падает, проверьте, что id-token: write объявлен на правильном уровне — у того job, который выполняет шаг публикации.

Почему это важно

Trusted Publishing убирает разрастание учётных данных и ротацию секретов в CI — именно за это команды и переходят. «Тихая» 400 при публикации отнимает массу времени, если вы не знаете про ограничение с переиспользуемыми workflows. Понимание этой детали позволяет заранее спланировать рефакторинг, а не гоняться за недиагностичной ошибкой.

Вывод

Если загрузка в Test PyPI или PyPI работает с паролем, но падает с Trusted Publisher и возвращает 400, проверьте, не запускается ли публикация через переиспользуемый workflow. Сейчас PyPI такой сценарий не поддерживает. Перенесите job публикации в обычный workflow в доверенном репозитории, убедитесь, что в месте выполнения публикации задано permissions: id-token: write, и не возвращайте пароль. Это небольшое структурное изменение приводит конвейер в соответствие с требованиями PyPI к Trusted Publishing и возвращает чистый путь релиза без секретов.