Workflow commands и summaries
Environment files `GITHUB_ENV`, `GITHUB_OUTPUT`, `GITHUB_PATH`, `GITHUB_STEP_SUMMARY`, группировка логов, warnings/errors, masking и удобные Markdown-отчеты в run summary.
Workflow commands и summaries
Workflow commands — это канал связи между вашим run-скриптом и GitHub Actions runner. Обычный echo пишет строку в лог, а специально отформатированный echo или запись в environment file меняет состояние job: добавляет env-переменную, задаёт output, расширяет PATH, создаёт annotation или пишет Markdown в summary.
Их удобно считать тонким слоем «служебного протокола» поверх shell. Для данных между steps чаще используются environment files: GITHUB_ENV, GITHUB_OUTPUT, GITHUB_PATH, GITHUB_STEP_SUMMARY. Для читаемости логов — команды ::group::, ::warning::, ::error::, ::add-mask:: и ::stop-commands::.
Environment files: запись сейчас, эффект позже
Каждая из переменных GITHUB_ENV, GITHUB_OUTPUT, GITHUB_PATH и GITHUB_STEP_SUMMARY содержит путь к временному файлу на runner. Вы не редактируете YAML во время выполнения; вы дописываете строки в этот файл, а runner считывает их после завершения step.
Самая важная практическая деталь: step, который пишет в GITHUB_ENV или GITHUB_PATH, сам ещё не видит новое значение. Его увидят последующие steps в том же job.
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Compute build metadata
run: |
echo "BUILD_SHA=${GITHUB_SHA::7}" >> "$GITHUB_ENV"
echo "$HOME/.local/bin" >> "$GITHUB_PATH"
echo "Inside same step: BUILD_SHA=$BUILD_SHA"
- name: Use build metadata
run: |
echo "Later step: BUILD_SHA=$BUILD_SHA"
command -v my-tool || trueВ первом step BUILD_SHA ещё пустой, если он не был задан раньше. Во втором step он уже доступен как обычная env-переменная. Это отличается от Variables, env и secrets, где env: задаётся декларативно в YAML, и от Contexts и expressions, которые вычисляются в выражениях ${{ ... }} до или во время планирования частей workflow.
Есть ограничения. Нельзя перезаписывать стандартные переменные GITHUB_* и RUNNER_*; GITHUB_ENV также нельзя использовать для установки NODE_OPTIONS. Если нужно передать значение в другой job, GITHUB_ENV не подходит: job изолирован. Для этого нужны Step outputs и job outputs, artifacts или внешний storage.
GITHUB_OUTPUT: когда значение должно стать API step
GITHUB_OUTPUT задаёт outputs текущего step. Чтобы потом сослаться на них, у step должен быть id.
- name: Detect package manager
id: pkg
run: |
if [ -f pnpm-lock.yaml ]; then
echo "manager=pnpm" >> "$GITHUB_OUTPUT"
elif [ -f package-lock.json ]; then
echo "manager=npm" >> "$GITHUB_OUTPUT"
else
echo "manager=unknown" >> "$GITHUB_OUTPUT"
fi
- name: Install
if: ${{ steps.pkg.outputs.manager != 'unknown' }}
run: echo "Will install with ${{ steps.pkg.outputs.manager }}"Такой output хорош для короткого структурного значения: версия, путь, флаг, имя окружения, результат detection step. Для файлов он плох: кладите coverage, screenshots, bundles и test reports в Artifacts и reports. Для ускорения зависимостей используйте Dependency cache, а не outputs.
Многострочные значения записываются через delimiter-синтаксис:
{
echo 'REPORT<<EOF'
npm test -- --reporter=dot || true
echo 'EOF'
} >> "$GITHUB_OUTPUT"Delimiter не должен встретиться отдельной строкой внутри значения. Если содержимое произвольное, безопаснее записать его в файл и загрузить как artifact.
GITHUB_STEP_SUMMARY: важное отдельно от шумных логов
Логи нужны для диагностики, но читать их после каждого CI run неудобно. GITHUB_STEP_SUMMARY позволяет добавить Markdown в summary страницы workflow run. Это место для короткого результата: что проверялось, сколько тестов прошло, куда задеплоено, какой artifact скачать.
- name: Test and summarize
run: |
echo "## CI summary" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "| Check | Result |" >> "$GITHUB_STEP_SUMMARY"
echo "| --- | --- |" >> "$GITHUB_STEP_SUMMARY"
if npm test; then
echo "| Tests | Passed |" >> "$GITHUB_STEP_SUMMARY"
else
echo "| Tests | Failed |" >> "$GITHUB_STEP_SUMMARY"
exit 1
fiSummary поддерживает GitHub Flavored Markdown. Файл GITHUB_STEP_SUMMARY уникален для каждого step; когда job заканчивается, summaries steps группируются в job summary. Если summaries пишут несколько jobs, на странице workflow они упорядочиваются по времени завершения jobs, а не по порядку объявления в YAML.
Практический стиль: summary должен отвечать на вопрос «что мне делать дальше?». Например: «tests failed, смотри artifact playwright-report», «deploy в staging прошёл, URL такой-то», «cache miss, job занял 6m 12s». Не дублируйте туда весь лог. У summary есть лимиты: максимум 1 MiB на step и до 20 step summaries, показываемых на job. Если upload summary не удался из-за размера, GitHub создаст error annotation, но сам job от этого не станет failed.
Логи: groups, annotations и debug
Для длинных install/build steps используйте группы. Это не меняет выполнение, но резко улучшает чтение лога.
- name: Build
run: |
echo "::group::Install dependencies"
npm ci
echo "::endgroup::"
echo "::group::Run tests"
npm test
echo "::endgroup::"::notice::, ::warning:: и ::error:: создают annotations. Их можно привязать к файлу и строке:
echo "::warning file=src/config.ts,line=12,title=Deprecated config::USE_LEGACY_API still enabled"
echo "::error file=src/app.test.ts,line=44,title=Flaky test::Expected status 200, got 500"Annotation полезна, когда скрипт сам нашёл проблему и может указать на конкретное место. Но ::error:: — это ещё не exit 1. Если step должен упасть, завершайте команду ненулевым кодом.
::debug:: виден только при включённом debug logging через secret ACTIONS_STEP_DEBUG=true. Это хорошее место для дополнительной диагностики, которую не хочется показывать в обычном run.
Masking и stop-commands
::add-mask::{value} просит runner редактировать это значение в последующих логах. Ключевое слово — «последующих»: маску надо зарегистрировать до того, как значение напечатано или использовано в другой workflow command.
- name: Generate temporary token
run: |
token="temp-token-from-script"
echo "::add-mask::$token"
echo "Token generated and masked"Не полагайтесь на masking как на полноценную модель безопасности. Не печатайте secrets намеренно, не кладите их в cache или artifacts, не выводите целиком contexts вроде github. Это та же логика, что и в Secure use и threat model: masking снижает ущерб от случайного вывода, но не делает опасный workflow безопасным.
::stop-commands::{token} временно отключает распознавание workflow commands в stdout. Это нужно, когда вы печатаете произвольный скрипт, JSON или лог внешнего инструмента, где случайно может встретиться строка вида ::warning::....
marker=$(uuidgen)
echo "::stop-commands::$marker"
cat generated-script.sh
echo "::$marker::"Marker должен быть уникальным для run. Иначе кто-то может раньше времени снова включить команды в выводе.
Рабочая эвристика
Используйте GITHUB_ENV, когда значение нужно как env-переменная в следующих steps того же job. Используйте GITHUB_OUTPUT, когда значение является контрактом step и должно читаться через steps.<id>.outputs или подниматься в job outputs. Используйте GITHUB_PATH, когда step установил CLI в нестандартную директорию. Используйте GITHUB_STEP_SUMMARY, когда результат важнее подробного лога.
А для человека, который открывает failed run утром, оставляйте короткий summary, аккуратные groups и точные annotations. Это не косметика: это эксплуатационная часть нормального CI pipeline.
See also
- Variables, env и secrets — где проходит граница между
env,vars, secrets и contexts. - Step outputs и job outputs — как поднимать outputs из step в job и передавать их через
needs. - Artifacts и reports — когда результатом является файл или отчёт, а не короткая строка.
- Dependency cache — почему cache не является handoff-механизмом между steps или jobs.
- CI pipeline — где summaries и annotations помогают быстро понять статус проверки.
- Secure use и threat model — почему masking не заменяет least privilege и аккуратную работу с secrets.
- pull_request_target и untrusted PR — где случайный вывод и untrusted scripts становятся security-проблемой.
