Step outputs и job outputs
Как передавать значения между steps и jobs через `GITHUB_OUTPUT`, `steps.<id>.outputs`, `jobs.<id>.outputs` и `needs`; когда outputs лучше artifacts или cache.
Step outputs и job outputs
Step outputs и job outputs — это короткий канал передачи вычисленных значений внутри одного workflow run. Он нужен не для файлов и не для кеша зависимостей, а для небольших строковых результатов: версии релиза, имени Docker image tag, флага «надо ли публиковать», пути к сгенерированному manifest, id deployment или JSON-кусочка конфигурации.
На практике outputs закрывают промежуток между тем, что уже было в Variables, env и secrets, и тем, что будет в Artifacts и reports. env удобно передаёт значения в shell внутри job, artifacts передают файлы между jobs, cache ускоряет повторные runs. Outputs же передают маленькое вычисленное значение в expression-модель GitHub Actions: steps.<id>.outputs.* внутри job и needs.<job_id>.outputs.* между jobs.
flowchart TD
A[Step выполняет shell-команду] --> B[Запись name=value в GITHUB_OUTPUT]
B --> C[steps.<id>.outputs.name]
C --> D{Нужно значение в другом job?}
D -- Нет --> E[Следующий step того же job читает output]
D -- Да --> F[jobs.<job_id>.outputs мапит step output]
F --> G[Downstream job объявляет needs]
G --> H[needs.<job_id>.outputs.name]Step output: значение из одного step в следующие steps
Step output создаётся записью в environment file GITHUB_OUTPUT. Step, который задаёт output, должен иметь id, иначе к нему нельзя обратиться через steps.<id>.outputs.
name: Version metadata
on:
workflow_dispatch:
jobs:
meta:
runs-on: ubuntu-latest
steps:
- name: Compute version
id: version
run: |
echo "tag=1.4.${GITHUB_RUN_NUMBER}" >> "$GITHUB_OUTPUT"
echo "channel=preview" >> "$GITHUB_OUTPUT"
- name: Use step outputs
run: |
echo "Tag: $RELEASE_TAG"
echo "Channel: $RELEASE_CHANNEL"
env:
RELEASE_TAG: ${{ steps.version.outputs.tag }}
RELEASE_CHANNEL: ${{ steps.version.outputs.channel }}Здесь важны две стадии. Команда echo "tag=..." >> "$GITHUB_OUTPUT" выполняется на runner. После step GitHub Actions разбирает файл и добавляет tag в outputs этого step. Следующий step читает значение через expression ${{ steps.version.outputs.tag }} и, при желании, перекладывает его в обычную shell-переменную RELEASE_TAG.
Это не то же самое, что GITHUB_ENV. GITHUB_ENV делает environment variable доступной следующим steps того же job. GITHUB_OUTPUT делает именованный результат доступным через expression context. Если значение должно участвовать в if, with, name, env, job outputs или reusable workflow outputs, обычно удобнее output.
Job output: значение из job в другой job
Jobs изолированы: environment variables, созданные в одном job, не появляются в другом. Чтобы передать маленькое значение дальше, step output надо «поднять» на уровень job через jobs.<job_id>.outputs, а downstream job должен явно зависеть от него через needs.
name: Build and publish
on:
push:
branches: [main]
jobs:
prepare:
runs-on: ubuntu-latest
outputs:
image_tag: ${{ steps.meta.outputs.image_tag }}
should_publish: ${{ steps.meta.outputs.should_publish }}
steps:
- uses: actions/checkout@v4
- name: Compute build metadata
id: meta
run: |
short_sha="${GITHUB_SHA::7}"
echo "image_tag=ghcr.io/acme/api:${short_sha}" >> "$GITHUB_OUTPUT"
echo "should_publish=true" >> "$GITHUB_OUTPUT"
build:
needs: prepare
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Build image
run: docker build -t "$IMAGE_TAG" .
env:
IMAGE_TAG: ${{ needs.prepare.outputs.image_tag }}
publish:
needs: [prepare, build]
if: ${{ needs.prepare.outputs.should_publish == 'true' }}
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Publish image
run: echo "Publishing $IMAGE_TAG"
env:
IMAGE_TAG: ${{ needs.prepare.outputs.image_tag }}Схема чтения такая: prepare вычисляет step output steps.meta.outputs.image_tag; job prepare объявляет публичный job output image_tag; jobs build и publish читают его через needs.prepare.outputs.image_tag. Без needs: prepare context needs.prepare не существует.
Есть тонкость с транзитивными зависимостями. Если deploy зависит только от publish, а publish зависит от prepare, то deploy не получает автоматически needs.prepare.outputs.*. Для чтения output job должен быть прямой зависимостью, либо промежуточный job должен переэкспортировать нужное значение.
Outputs — это строки
В expressions outputs практически всегда надо воспринимать как строки. Даже если step пишет should_publish=true, downstream expression получает строку "true", поэтому безопасный вариант — сравнивать со строкой:
if: ${{ needs.prepare.outputs.should_publish == 'true' }}Если вы передаёте JSON, держите его маленьким и явно парсьте там, где это нужно:
jobs:
plan:
runs-on: ubuntu-latest
outputs:
matrix: ${{ steps.matrix.outputs.value }}
steps:
- id: matrix
run: |
echo 'value={"node":[20,22],"os":["ubuntu-latest"]}' >> "$GITHUB_OUTPUT"
test:
needs: plan
runs-on: ubuntu-latest
strategy:
matrix: ${{ fromJSON(needs.plan.outputs.matrix) }}
steps:
- run: node --versionТакой приём полезен для динамической Matrix strategy: один job решает, какие варианты тестировать, второй строит matrix из JSON. Но это не повод тащить в output большой manifest. У job outputs есть лимиты размера; большие данные лучше передавать как artifact.
Multiline outputs и quoting
Для простых значений достаточно строки name=value. Для multiline-значения используется heredoc-форма, похожая на multiline GITHUB_ENV:
- name: Produce Markdown notes
id: notes
run: |
{
echo 'body<<EOF'
echo '### Release notes'
echo ''
echo '- Fixed auth redirect'
echo '- Updated Docker base image'
echo 'EOF'
} >> "$GITHUB_OUTPUT"Но multiline outputs надо использовать аккуратно. Если значение содержит произвольный пользовательский текст, случайный delimiter вроде EOF теоретически может встретиться внутри значения. Для недоверенного или длинного текста лучше записать файл и передать его через Artifacts и reports, а не через output.
Когда output лучше artifact или cache
Output лучше artifact, когда downstream job нужен не файл, а короткое решение: какой tag собрать, какой environment выбрать, какой id передать в API, запускать ли deploy. Output виден прямо в expressions, поэтому им удобно управлять if, strategy.matrix, with и env.
Artifact лучше output, когда нужно передать файл или набор файлов: build bundle, coverage, junit XML, generated docs, SBOM, screenshots, logs. Artifact сохраняется после job и может быть скачан другим job или человеком из workflow run.
Cache — вообще другой инструмент. Он не передаёт «результат сборки как контракт» между jobs, а ускоряет повторные runs за счёт сохранения зависимостей или build outputs по ключу. Если job B должен гарантированно получить ровно файл, созданный job A в этом run, это artifact. Если job B должен узнать image_tag, это output. Если следующий workflow run хочет быстрее установить npm-зависимости, это Dependency cache.
Практические ограничения и ошибки
Не используйте outputs как канал для secrets. Job outputs срабатывают в orchestration-слое workflow, могут попадать в expressions и передаваться дальше большему числу steps, чем вы изначально планировали. Кроме того, GitHub редактирует outputs, которые выглядят как secrets, и может не отправить такой output дальше. Для credentials лучше использовать secrets, environment secrets, Permissions и GITHUB_TOKEN или OIDC-схемы из OIDC и секреты облаков.
Не забывайте id у step. name нужен человеку в UI, id нужен YAML-выражениям. Это разные поля.
Не рассчитывайте на порядок matrix jobs. Если matrix job пишет outputs с одинаковыми именами, последний завершившийся вариант может перезаписать значение, а порядок GitHub Actions не гарантирует. Делайте имена уникальными, например output_${{ matrix.version }}, или складывайте результаты в artifacts и агрегируйте отдельным job.
Не превращайте outputs в глобальное состояние workflow. Хороший output читается как маленький контракт: image_tag, release_version, deploy_url, should_publish, coverage_percent. Если output называется data и содержит большой JSON со всем подряд, workflow быстро становится хрупким.
See also
- Variables, env и secrets — где заканчиваются обычные переменные и начинается передача результатов.
- Workflow commands и summaries — environment files
GITHUB_OUTPUT,GITHUB_ENV,GITHUB_STEP_SUMMARYи команды логов. - Artifacts и reports — передача файлов, отчётов и build outputs между jobs.
- Dependency cache — ускорение повторных runs через cache keys и restore keys.
- Jobs, dependencies и conditions — как
needsзадаёт граф выполнения jobs. - Contexts и expressions — почему
${{ needs.* }}и shell variables живут на разных стадиях. - Matrix strategy — динамические matrix-сценарии через JSON outputs.
- Reusable workflows — как outputs поднимаются ещё выше, до outputs reusable workflow.
