Как перестать отлаживать GitHub Actions через бесконечные коммиты

21 авг 2026
4,153
259
11
1 месяц

Знакомая история: правишь YAML-конфиг пайплайна, делаешь коммит с сообщением fix ci, пушишь, ждешь пару минут, ловишь красный крестик из-за опечатки в названии параметра. Следом идут коммиты fix ci 2, testing action, why it fails и finally working. Цикл обратной связи в CI/CD часто превращается в пытку просто потому, что синтаксис проверяется уже на удаленном раннере.

Я регулярно наступал на эти грабли, пока не наткнулся на actionlint. Это статический анализатор для workflow-файлов GitHub Actions, написанный на Go разработчиком под ником rhysd.

Утилита проверяет файлы локально за доли секунды и находит ошибки задолго до того, как код попадет в удаленный репозиторий.

actionlint reports 7 errors

Что обычно ломается в пайплайнах

Большинство линтеров для YAML просто проверяют базовый синтаксис разметки: отступы, кавычки, списки. Но валидный YAML вполне может быть абсолютно нерабочим пайплайном для GitHub Actions.

actionlint идет глубже и парсит саму семантику конфигураций. Вот типичный пример сомнительного воркфлоу из документации проекта:

Реклама
on:
  push:
    branch: main
    tags:
      - 'v\d+'
jobs:
  test:
    strategy:
      matrix:
        os: [macos-latest, linux-latest]
    runs-on: ${{ matrix.os }}
    steps:
      - run: echo "Checking commit '${{ github.event.head_commit.message }}'"
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node_version: 18.x
      - uses: actions/cache@v4
        with:
          path: ~/.npm
          key: ${{ matrix.platform }}-node-${{ hashFiles('**/package-lock.json') }}
        if: ${{ github.repository.permissions.admin == true }}
      - run: npm install && npm test

На первый взгляд файл выглядит нормально. Обычный синтаксический чекер пропустит его без замечаний. Если натравить на него actionlint, линтер моментально выдаст сразу семь ошибок:

  1. Неверный ключ branch вместо branches в секции push.
  2. Попытка использовать регулярное выражение \d+ в фильтре тегов, где гитхаб принимает только glob-паттерны.
  3. Несуществующая метка раннера linux-latest (вместо ubuntu-latest).
  4. Потенциальная уязвимость script injection: текст коммита подставляется напрямую в inline-скрипт без экранирования через переменные окружения.
  5. Опечатка в параметре node_version (экшен actions/setup-node ждет node-version через дефис).
  6. Обращение к несуществующему полю matrix.platform, когда в матрице объявлена переменная os.
  7. Ошибка типов в выражении условия if.

Что умеет проверять утилита

Внутри actionlint заложено несколько независимых уровней анализа.

Строгая типизация контекстов и выражений

В GitHub Actions выражения вида ${{ ... }} вычисляются на лету. Ошибиться в них проще простого: обратиться к полю, которого нет в контексте события, или перепутать типы данных. actionlint знает схему контекстов GitHub Actions (github, env, matrix, steps и других) и валидирует типы выражений. Если попытаться вызвать метод у строки или обратиться к несуществующему выходу шага (steps.my_step.outputs.foo), линтер сразу об этом скажет.

Валидация популярных экшенов

Линтер умеет проверять соответствие входных параметров (with:) реальным спецификациям используемых экшенов. Он знает метаданные популярных действий из официального каталога и подтягивает их схему, чтобы подсветить лишние или неправильно названные параметры.

Проверка встроенных скриптов

Если в шаге run: написан bash-скрипт, actionlint вызывает ShellCheck для проверки логики команд, поиска незакавыченных переменных и типичных багов в шелл-скриптах. Если в шаге используется Python, линтер умеет подключать pyflakes. При этом утилита аккуратно мапит позиции ошибок внутри многострочных скриптов прямо на строки исходного YAML-файла.

Поиск проблем безопасности

Одна из самых неприятных уязвимостей в CI/CD — выполнение произвольного кода через данные из pull request или заголовков коммитов. Когда мы пишем run: echo "${{ github.event.pull_request.title }}", злоумышленник может внедрить в название PR точку с запятой и выполнить любую команду с правами раннера. actionlint отслеживает такие ненадежные источники данных и требует передавать их через переменные окружения.

Варианты запуска

Утилиту можно запускать разными способами в зависимости от привычного воркфлоу.

Самый простой вариант — поставить бинарник через Go:

go install github.com/rhysd/actionlint/cmd/actionlint@latest

После установки достаточно запустить команду в корне репозитория:

actionlint

Она сама найдет папку .github/workflows/ и проверит все вложенные манифесты.

Если не хочется ничего ставить на хост, проект доступен в виде Docker-образа, плагина для VS Code, хука для pre-commit и интеграции для утилиты reviewdog. Автор также собрал онлайн-песочницу на WebAssembly, где можно вставить свой YAML в браузере и посмотреть результаты анализа без терминала.

Настройка под свои нужды

В реальных проектах часто используются self-hosted раннеры со специфичными метками или кастомные переменные окружения. Чтобы линтер не ругался на неизвестные метки машин, достаточно создать конфигурационный файл .github/actionlint.yaml:

self-hosted-runner:
  labels:
    - gpu-runner
    - arm-builder

В этом же конфиге можно отключать отдельные правила или задавать шаблоны игнорирования для конкретных файлов.

Кому пригодится

Если вы поддерживаете больше одного пайплайна в GitHub Actions, actionlint бережет нервы и экономит платные минуты раннеров. Утилита легкая, не требует настройки Node.js или тяжелых рантаймов, запускается моментально и закрывает 90% глупых опечаток еще на этапе git commit. Самый разумный шаг — повесить ее в pre-commit хук или отдельным шагом в pull request checks.

🍪 Мы используем файлы cookie и сервис аналитики Яндекс.Метрика, чтобы сайт работал лучше. Продолжая пользоваться devtrends.ru, вы соглашаетесь с обработкой данных согласно Политике конфиденциальности.