GitHub Actions Compatibility reference

GitHub Actions Compatibility runs a supported subset of GitHub Actions workflows as native Buildkite Pipelines jobs. Use this reference to check whether your workflow can run, what needs to change, and which restrictions apply.

This page covers the current released buildkite-gha runtime and server-side workflow selection, distinguishing advanced runtime capabilities from native setup defaults. Minimum versions and required pipeline settings or server capabilities are noted with each feature. Treat unlisted functionality as unsupported.

For setup without configuring a plugin or installing the runtime, see GitHub Actions Compatibility. For optional diagnostics and advanced custom importers, see the CLI reference. The security guide describes credential and job-isolation requirements.

GitHub's workflow syntax reference describes the original syntax. This page describes the subset that runs on Buildkite Pipelines.

Compatibility at a glance

Use this table to identify migration work, then check the linked restrictions and setup. Accepted, no effect means the syntax doesn't change execution. Browse or report compatibility issues.

Feature Support Main restriction or setup Details
Feature Runners and tools Support Linux x86-64/arm64, macOS arm64, Windows Server 2022 x86-64 Main restriction or setup Agent v3.129+; Linux arm64 needs an explicit queue mapping and support contact for native setup. Labels don't reproduce GitHub images or tools. Details Runners and tools
Feature Events and filters Support Listed events and bounded filters Main restriction or setup Native manual runs require matching push without workflow_dispatch. Delivery can require backend support and webhook setup. Path filters need linked data and local history. Details Events and triggers
Feature Actions Support Local and public JavaScript/composite actions; Linux Docker actions Main restriction or setup Private actions and direct workflow docker:// calls are unsupported. Admission doesn't guarantee GitHub service access. Details Action sources and runtimes
Feature Checkout Support Native adapter for the event repository Main restriction or setup Pull requests check out the head, not a merge. No alternate repositories or persisted credentials. Details Checkout action
Feature Artifacts Support Native ZIP upload and download Main restriction or setup Downloads require verified direct needs producers; no cross-run/repository or GitHub artifact API access. Details Upload, download
Feature Caching Support Admitted cache-v2 clients use Buildkite Results Main restriction or setup Client versions and tools matter. Cache mode is best-effort, not authorization. Details Cache action
Feature Repositories and GitHub tokens Support Public GitHub; authorized private GitHub/Origin access Main restriction or setup Public workflow-token support requires eligible hosted GitHub.com jobs, the token setting enabled, and top-level permissions. Details Repositories, GitHub token
Feature Secrets and OIDC Support Buildkite Secrets; Buildkite-issued OIDC Main restriction or setup GitHub secrets aren't copied. Cloud trust must use the Buildkite issuer and claims. Details Secrets and OIDC
Feature Deployment environments Support Literal top-level-job names and approval gates Main restriction or setup GitHub reviewer identity isn't enforced; several protection rules are rejected. URL is accepted but ignored; no deployment records are created. Details Deployment environments
Feature Reusable workflows Support Local, public, and approved private sources Main restriction or setup Four-level nesting; private source access is off by default in native setup and requires support contact. Remote secret forwarding is unsupported. Details Reusable workflows
Feature Jobs, matrices, steps, and expressions Support Listed syntax and bounded dynamic forms Main restriction or setup Context access varies by field. Deferred expansion has source, graph, and scheduling limits. Details Workflow syntax reference, jobs, steps, expressions
Feature Concurrency and fail-fast Support Queued concurrency groups; fail-fast syntax accepted Main restriction or setup Workflow cancel-in-progress: true warns but doesn't cancel. Matrix fail-fast has no cancellation effect. Details Concurrency, matrices

Important differences from GitHub Actions

Buildkite Pipelines reads workflow event declarations and creates one build per matching workflow. Workflow files remain in .github/workflows/; the server manages selection and preparation. This doesn't create a shadow GitHub Actions run.

GitHub Actions concept Buildkite Pipelines behavior
GitHub Actions concept Trigger Buildkite Pipelines behavior Server-side matching of supported GitHub events, or a manual workflow selection
GitHub Actions concept Workflow run Buildkite Pipelines behavior One Buildkite build per selected workflow
GitHub Actions concept Job or matrix instance Buildkite Pipelines behavior Buildkite command job
GitHub Actions concept needs Buildkite Pipelines behavior Dependencies with verified upstream results and outputs
GitHub Actions concept Step Buildkite Pipelines behavior Runs within the job, sharing its workspace, environment files, state, and cleanup

Check these differences before migrating:

  • Action access: Private actions are unsupported, even inside private reusable workflows. An admitted public action can still depend on unsupported GitHub services.
  • Runner parity: Labels select a platform, not GitHub image, toolchain, hardware, or Xcode parity. Supply tools through your queue and image.
  • Pull request checkout: Workflows and checkout use the head commit, not GitHub's synthetic merge commit. Review events also use the head, not the reviewed commit.
  • Secrets and tokens: GitHub secrets aren't copied. Migrate them to Buildkite Secrets. Public GitHub workflow-token support requires eligible hosted jobs and the pipeline's token setting enabled; immutable top-level permissions apply to every receiving job.
  • Cloud identity: OIDC uses the Buildkite issuer and claims, not GitHub's. Update cloud trust before deploying.
  • Deployment approval: Environment gates don't enforce GitHub reviewer lists, self-review restrictions, or administrator bypass. Any user who can unblock the pipeline can approve.
  • Cancellation: Workflow cancel-in-progress doesn't cancel superseded builds. Matrix fail-fast is accepted but has no cancellation effect.
  • Trigger coverage: pull_request_target is intentionally unsupported. Pipeline triggers don't support repository_dispatch, workflow_run, check_run, or check_suite. Check event delivery and filtering, including accepted-but-ignored filters, rather than assuming every GitHub trigger runs.

Check your workflow

Check syntax, triggers, and the static graph without an event:

buildkite-gha validate .github/workflows/ci.yml

Apply the production profile with an exact event snapshot:

buildkite-gha validate \
  --profile hosted \
  --event-path .buildkite/events/current.json \
  .github/workflows/ci.yml

--event generates a minimal supported snapshot instead of reading --event-path; the options are mutually exclusive. --all-events checks every declared supported event separately. Generated snapshots don't cover every activity/payload and can't substitute for linked webhook/local-diff evidence.

Result Meaning
Result Compilable Meaning Syntax, triggers, and static graph can be translated; not proof of hosted admission.
Result Not applicable Meaning The selected event isn't declared; upload skips the workflow.
Result Admitted Meaning Resolved actions and generated plans pass production policy; action code hasn't been executed.
Result Context required Meaning Compilation/policy checks pass, but this input lacks evidence for admission, such as path-filter webhook data.

Admission isn't execution, and a runtime release isn't proof of the version selected by a hosted build. Use the CLI reference for representative snapshots and reports, and check the build's importer/runtime version when diagnosing differences.

Runners and tools

Buildkite agent v3.129 or newer is required. Generated jobs support Linux x86-64, Linux arm64 with an explicit queue mapping, native macOS arm64, and Windows Server 2022 x86-64. Contact the Buildkite Support team for Linux arm64 mapping in native pipeline setup. Buildkite Pipelines manages workflow preparation on Linux x86-64 or macOS arm64; an advanced custom importer can also run on Linux arm64. Windows agents run generated jobs only.

Queue selection and mappings

Native pipeline setup uses automatic runner resolution and doesn't expose custom mappings or generated-job cache volume configuration. Contact the Buildkite Support team if automatic resolution doesn't meet your requirements. Workflow-level caching is described under Cache action.

Advanced custom importers can use explicit CLI mappings to override automatic selection. They declare Linux x86-64 except for known Linux arm64 labels (ubuntu-24.04-arm, ubuntu-22.04-arm, and suffixes -arm64 or -aarch64), known macOS labels, and Windows labels. Linux arm64 requires an explicit queue; arm64, macOS, and Windows mappings reject images. macOS x86-64 is unsupported.

During upload the job-scoped Agent API validates mapped queues in the importing cluster, including OS and architecture for hosted queues. Self-hosted validation checks existence only; operators must supply the declared platform. Mapped queue, image, and cache settings are preserved. Mapping validation must be acknowledged by a compatible backend or import fails.

Other selectors use the complete Agent API target and any fallback warnings. Eligible Linux queues can select native GitHub Actions images without a step image; immutable image targets remain supported elsewhere. Job container.image is separate from the host image. Labels don't provision queues or promise provider hardware, tools, caches, or networking.

Runner rejection Next step
Runner rejection missing_queue Next step Create the named hosted queue or configure a compatible mapping.
Runner rejection incompatible_labels Next step Correct unsupported labels; for Windows, also check eligibility and routing.
Runner rejection no_cluster Next step Use a cluster containing compatible queues.
Runner rejection queue_not_found Next step Correct the mapping to an active queue in the importing cluster.
Runner rejection queue_platform_mismatch Next step Map to a matching hosted OS and architecture.

An explicit rejection doesn't fall back to presets. Without mappings, an unreachable API can warn and fall back to local presets: ubuntu-latest, ubuntu-24.04, ubuntu-22.04, and macos-latest. Linux presets use corresponding Noble or Jammy hosted-toolchains images; configured immutable images can override them. Offline validate and compile don't verify cluster queues. validate --profile hosted admits only these local presets.

An advanced custom importer's explicit mapping can attach one hosted cache volume, outside workflow syntax. Job containers and Windows jobs don't support these volumes.

Runner tools

Host tools come from the selected queue/image, not the label alone. macOS and Windows must provide shell tools and compatible native dependencies. Generated Linux jobs use a dedicated runner user. If you manage a custom execution environment, see the runtime bootstrap requirements.

From v0.105.2, host shell, JavaScript, and composite steps inherit host JAVA_HOME when set. Explicit workflow/job/step values and GITHUB_ENV, including empty values, override it. This doesn't add JAVA_HOME to the expression env context, forward it to containers, select/install Java, or inherit version-specific or Java option variables such as JAVA_TOOL_OPTIONS.

Linux tool cache selection follows the runner target. Hosted cache requests use /opt/hostedtoolcache, which must exist; targets disabling it use a job-private cache without changing preinstalled host tools. Native Namespace targets use private caches. Older image targets without a cache choice retain their hosted-toolcache behavior. macOS bootstrap creates writable /Users/runner/hostedtoolcache and needs noninteractive sudo, preserving prefixes required by actions such as ruby/setup-ruby. Windows uses a job-private cache.

On native Namespace runners, the runtime enables default-load=true for stored single-node buildx builders using remote, docker-container, or kubernetes, so plain docker build loads the image for later docker run. Explicit --push or --output still controls output. Multi-node builders aren't changed and can still require explicit output/load choices. Setup problems warn rather than fail the job.

Windows jobs

windows-latest and windows-2022 support Windows Server 2022 x86-64 through an explicit queue mapping or enabled Agent API routing. There is no local Windows preset or silent Linux fallback. The queue must provide PowerShell 7 (pwsh) and Git on PATH. Buildkite hosted Windows access is separate from runtime support.

With Windows routing enabled, windows-2025 and Depot depot-windows-2025 labels, including -4, -8, -16, -32, and -64, can resolve through the Agent API to the same windows-medium queue. A warning explains that execution may use Server 2022 and doesn't preserve Depot hardware, caches, or networking. These aliases require an eligible existing hosted queue and Agent API resolution; they have no local preset or explicit Windows mapping. Native Server 2025 and Windows arm64 execution remain unsupported.

Runtime boundaries

Windows defaults to pwsh. Explicit pwsh, Windows PowerShell (powershell), MSYS2 custom shells, JavaScript actions, and composite actions are supported. cmd, containers, services, Docker actions, custom images, and cache volumes are unsupported.

Use UTF-8 for environment files. Windows PowerShell needs Out-File -Encoding utf8 -Append, not its default UTF-16 redirection. Environment names match case-insensitively, but the winning assignment's spelling is preserved. This includes the last assignment through GITHUB_ENV. Runtime identity variables remain protected from overrides.

Configure a Windows runner

For a native GitHub Actions pipeline, confirm hosted Windows Medium access and automatic routing availability with the Buildkite Support team at support@buildkite.com. The pipeline's cluster needs an active hosted Windows AMD64 Medium queue named windows-medium. A self-hosted, differently sized, wrong-platform, or other-cluster queue doesn't qualify.

Advanced custom importers can use explicit CLI mappings for windows-latest or windows-2022. These aren't part of native setup. The Agent API validates that the queue exists in the importing job's cluster and, for hosted queues, matches windows/amd64.

Runner resolution doesn't provision queues, grant access, or bypass trust policy. Isolate generated-job queues from protected credentials and previous jobs, including on self-hosted agents. Don't change a Windows-only job to Linux just to pass admission.

Diagnostic Next step
Diagnostic No Windows runner target is configured Next step Check hosted Windows access and routing with the Buildkite Support team. Offline validation has no Windows preset; this failure alone doesn't establish runtime incompatibility.
Diagnostic missing_queue Next step Check the importing cluster and hosted queue requirements, or map a compatible existing queue.
Diagnostic no_cluster Next step Move the pipeline to a cluster containing compatible queues.
Diagnostic Queue not found or platform mismatch Next step Correct the mapping to an active compatible queue in that cluster.
Diagnostic Incompatible labels Next step Check the requested variant. For windows-latest or windows-2022, also check hosted access and routing availability.
Diagnostic Windows variant has no local mapping Next step Use Agent API routing for the documented 2025 aliases, or select a 2022 label only if Server 2022 is suitable.

For support, include the build or job URL, workflow path, runs-on, importer version, and complete diagnostic. A generic rejection alone doesn't identify an entitlement, queue, or runtime problem.

Events and triggers

Buildkite Pipelines can match incoming GitHub events against workflow on declarations and create a build for each matching workflow. Alternatively, explicitly selected workflows run inside builds created by other Buildkite triggers, schedules, or manual requests. Event support differs between these routes. Start with the event table, then check delivery requirements, activity defaults, and filters. Event selection doesn't grant tokens or secrets.

Native manual runs currently require a matching push declaration without workflow_dispatch. If workflow_dispatch is declared, the server selects it, but the released runtime rejects it during preparation. See Run a workflow manually for the release boundary and supported alternatives. Compiler support for dispatch inputs doesn't establish native manual-run support.

Event Selection, defaults, and limits
push branches, branches-ignore, tags, and tags-ignore select their ref kind. Include arrays support ordered negative patterns. Branch-push path filters require the local-diff conditions. Tag pushes ignore path filters.
pull_request Pipeline triggers reject fork pull requests before creating a build. Branch filters match the base branch. Default activities are opened, synchronize, and reopened. Explicit supported activities are listed below. Path filters require linked data and local history. Tag and workflow filters are rejected. Checkout uses the head commit, not the synthetic merge.
merge_group Pipeline triggers require a compatible backend. Native builds require merge queue builds and Merge groups webhook delivery in pipeline GitHub settings. Branch filters match the base branch; default activity is checks_requested. Explicit destroyed is supported with the qualification below. Path filters are ignored with a warning. Tag and workflow filters and unknown activities are rejected.
release Pipeline triggers accept published, unpublished, created, edited, deleted, prereleased, and released, all by default. Native builds require Additional Webhooks > Releases and Code trigger mode and deliver only published, created, and released; other selections warn with W_NATIVE_RELEASE_ACTIVITIES_UNDELIVERED. Draft created, edited, deleted, and unpublished deliveries don't run.
deployment, deployment_status Pipeline triggers with a compatible backend, or explicit snapshots. No activity types or other event filters. Statuses are error, failure, in_progress, queued, pending, success, and waiting; inactive doesn't run. Use conditions on payload environment or state, not trigger environment filters. No deployment creation or environment orchestration is added.
create, delete Branch and tag lifecycle. No activity types or other filters.
label Repository label lifecycle. created, edited, and deleted, all by default.
fork Repository forks. No nonempty activity types or other filters.
public Repository visibility. No nonempty activity types or other filters.
gollum Wiki pages. No nonempty activity types or other filters.
page_build GitHub Pages builds. No nonempty activity types or other filters.
watch Repository stars. Only started, also the default.
milestone Milestone lifecycle. created, closed, opened, edited, and deleted, all by default.
branch_protection_rule Branch protection rules. created, edited, and deleted, all by default.
discussion, discussion_comment Discussions and comments. All listed activities by default.
issues All GitHub Actions issue activities by default: opened, edited, deleted, transferred, pinned, unpinned, closed, reopened, assigned, unassigned, labeled, unlabeled, locked, unlocked, milestoned, demilestoned, typed, untyped, field_added, and field_removed. Explicit types selects a subset.
issue_comment Issue and pull request conversation comments. created, edited, and deleted, all by default. Pipeline triggers don't inherit native command-word, trusted-commenter, or pull-request-only gating.
pull_request_review Pipeline triggers support submitted, edited, and dismissed, all by default. types selects activities, not review states. For approval-only execution, use if: github.event.review.state == 'approved'.
pull_request_review_comment Pipeline triggers support inline diff-comment created, edited, and deleted, all by default. Distinct from submitted reviews and conversation comments.
workflow_dispatch Custom importers can select this event from an explicit snapshot or authoritative GITHUB_EVENT_NAME or BUILDKITE_GITHUB_EVENT. Native manual runs currently fail during preparation. Branch, tag, type, and workflow filters are unsupported.
schedule Buildkite Pipelines owns cron configuration. Every on.schedule workflow is eligible for every scheduled build; the runtime can't identify which cron expression started it.
workflow_call Reusable-workflow interface, not a top-level trigger for reusable-only files.
pull_request_target Intentionally unsupported. Its privileged base-repository execution model requires a separate trust boundary. Switching to pull_request alone doesn't make untrusted checkout safe.
repository_dispatch, workflow_run, check_run, check_suite Unsupported by pipeline triggers. Imported workflows don't create GitHub workflow runs or emit workflow-run events. Native builds on completed check runs are a separate integration. Consider Buildkite trigger steps for cross-pipeline orchestration.
Other events Warn with W_TRIGGER_EVENT_UNSUPPORTED when mixed with supported events. Unsupported-only workflows fail event-independent validation and become skipped items on upload.

Explicit pull_request activities may be assigned, unassigned, labeled, unlabeled, opened, edited, closed, reopened, synchronize, converted_to_draft, locked, unlocked, enqueued, dequeued, milestoned, demilestoned, ready_for_review, review_requested, review_request_removed, auto_merge_enabled, or auto_merge_disabled.

Review events require v0.64.0 or later and don't support fork pull requests. Workflows and checkout use the head SHA, not the reviewed commit or synthetic merge commit; the ref remains refs/pull/<number>/merge. No default-branch-only requirement applies. Use payload head and base refs for branch conditions. CI-skip commit directives don't suppress review events. Same-repository reviews retain the pull request contents: read token ceiling, including approvals.

Release pipeline triggers require the GitHub Code Access App and use the peeled tag commit with refs/tags/<tag_name>. The tag, build, payload, and workflow identities must agree. Native builds without the App resolve the provided symbolic HEAD from checkout as a compatibility fallback. Deployment events use deployment.sha and its branch or tag ref. SHA-only deployments have empty Actions ref and ref_name, with workflow identity <owner>/<repo>/<path>@<sha>; no branch tip is substituted. Genuine deployment and status payload fields remain available in github.event.

Merge-group destruction

Explicit selection supports either or both activities:

on:
  merge_group:
    types:
      - checks_requested
      - destroyed
    branches:
      - stable

Omitted, null, or empty types selects only checks_requested. The runtime doesn't restrict destroyed by reason. Pipeline trigger delivery requires a compatible backend and retained original payload; CLI acceptance alone doesn't establish hosted availability. Check backend availability with support if this event isn't delivered.

Both activities retain the speculative head ref, head SHA, and immutable workflow identity. Default checkout fetches the recorded SHA even after queue-ref deletion; it doesn't substitute the base branch. Execution still depends on GitHub retaining that object. Don't assume automatic-cancellation behavior matches GitHub for every destruction reason. Hosted merge-queue workflow-token requests remain denied, including for descendants; tokenless workflows can run.

The repository events in the following sections require a compatible pipeline trigger backend, original linked payload, and pinned workflow identity. Unless stated otherwise, workflow selection and checkout use the source repository's resolved default-branch commit. Explicit snapshots must supply that resolved repository.default_branch; the runtime doesn't resolve mutable refs. Missing, malformed, foreign, or contradictory identity fails, including on rebuilds. Existing webhooks may need an added event subscription. These events don't grant token or secret access.

Repository fork events

fork runs for a fork of the source repository, never on the forkee's commit. The payload retains github.event.forkee. Explicit snapshots also need a full commit SHA. This doesn't enable fork pull requests or pull_request_target.

Repository visibility events

public runs when a private repository becomes public. The payload must identify the source repository, mark it public, and omit action.

Wiki page events

gollum requires a nonempty pages array with valid names, actions, and wiki commit SHAs. Page data remains in github.event.pages; execution doesn't use a wiki commit or add a wiki checkout.

GitHub Pages build events

page_build requires the Pages build ID, commit, and status. Failed builds can trigger workflows. github.event.build retains status and error data. Execution doesn't use the Pages build commit or deploy Pages.

Repository star events

watch selects stars (started), not notification subscriptions. Removing a star doesn't trigger workflows. The payload remains available through github.event.

Milestone lifecycle events

milestone selects the activities in the event table, not issue or pull request milestone assignment. Milestone data remains in github.event.milestone.

Branch protection rule events

branch_protection_rule requires a positive rule ID, a name, and a repository_id matching the source repository. Execution doesn't use the rule's branch pattern. Rule data remains in github.event.rule.

Discussion events

discussion supports created, edited, deleted, transferred, pinned, unpinned, labeled, unlabeled, locked, unlocked, category_changed, answered, unanswered, closed, and reopened. discussion_comment supports created, edited, and deleted.

The payload needs a discussion ID, number, and title; comments need a positive ID and matching discussion_id. There is no command-word or trusted-commenter gating. Transfers produce separate source transferred and destination created events; each uses that repository's default branch. Original discussion and comment data are retained. GitHub lists these webhooks as public preview.

Repository label lifecycle events

label selects repository label creation, editing, or deletion, not issue or pull request labeled actions. Label data remains in github.event.label.

Branch and tag lifecycle events

create and delete refer to Git refs, not repository creation or deletion. GitHub doesn't deliver them when more than three tags are created or deleted at once.

create uses the created ref's resolved commit, peeled for annotated tags. delete uses the resolved default branch, never the deleted ref; the deleted name remains in github.event.ref, while github.ref names the default branch. Explicit snapshots need a full SHA and matching repository identity.

Ignored event filters

Except for push, pull_request, merge_group, workflow_dispatch, and schedule, the webhook events listed above accept but ignore branches, branches-ignore, tags, tags-ignore, paths, and paths-ignore. These filters don't protect execution or credentials. Activity selection and identity checks still apply. Unknown keys and unsupported activities are rejected. pull_request and merge_group tag filters remain unsupported.

Pipeline trigger issues and issue_comment builds select the latest verified default-branch SHA without native Buildkite issue/comment build gating, branch filters, or path filters. Native issue builds remain supported with linked payloads and their own build-creation settings.

Activity defaults

Scalar or array event declarations, null event configuration, and empty event maps select the defaults in the event table. Where activity selection is supported, omitted types, literal types: null, and an empty types array also select those defaults. A nonempty string or array selects its exact supported activities. Null normalization for issues, issue_comment, label, release, both review events, pull_request, and merge_group requires v0.90.0 or later and a compatible backend.

Defaults aren't all activities: pull requests exclude edited and closed; merge groups exclude destroyed. Quoted 'null', arrays containing null, malformed types, and unknown activities are rejected. This doesn't admit activity filters on events that reject them, such as push, create, delete, or deployment events. fork, public, gollum, and page_build accept null or empty types but reject nonempty types.

Push path filters

Branch-push paths and paths-ignore require linked GitHub webhook data, complete non-shallow local history, and checkout and workflow content matching the pushed commit. The webhook repository, ref, commit range, force state, and complete commit set must match the build. origin must match the repository, but its current branch tip may have advanced.

Normal and force pushes compare before..after. A new branch uses the parent of the oldest pushed commit only when the complete pushed set has one clear single-parent boundary. Added, modified, deleted, and type-changed paths can match. Ordered positive and negative patterns apply as for pull requests.

A new branch with an explicitly empty webhook commits array has no changed paths. Workflows with paths or paths-ignore are skipped; workflows without path filters remain eligible. This does not apply to missing commit data or force pushes.

Unsupported cases fail rather than guessing whether to run:

  • Deleted refs, non-GitHub sources, missing or shallow history, or mismatched identities
  • Incomplete pushed-commit data, ambiguous new-branch history, or more than 1,000 pushed commits
  • More than 3,000 changed files or 2 MiB of Git changed-path output
  • Renames, diffs containing both additions and deletions, invalid patterns, or malformed Git output
  • Explicit or generated snapshots and environment fallbacks instead of linked webhook data

A verified nonmatch produces a skipped workflow without running its jobs. Tag pushes ignore path filters. GitHub's run-anyway fallback for more than 1,000 commits or diff timeouts isn't reproduced.

Diagnostic detail Next step
Diagnostic detail Requires linked webhook data Next step Use a GitHub push build with the original payload, not a manual build or snapshot.
Diagnostic detail Requires complete non-shallow checkout Next step Check git rev-parse --is-shallow-repository in the importer. Ask the agent administrator to fetch full history there if shallow. Workflow checkout runs too late to fix filtering.
Diagnostic detail Requires complete pushed commits, before commit unavailable, or rename-conformance restriction Next step Contact support with the build URL and full diagnostic. Workflow checkout settings can't supply missing webhook data or remove compatibility limits.
Diagnostic detail More than 1,000 commits or 3,000 files Next step Split the push or diff into smaller changes.

If a checkout isn't shallow or the history check fails unexpectedly, contact support. Removing a path filter changes which workflows run; it isn't a repair for missing evidence.

Pull request path filters

Ordered GitHub patterns can include and then exclude paths:

on:
  pull_request:
    paths:
      - "src/**"
      - "!src/generated/**"

The importer compares pinned base and head commits using base...head. Linked data must include full SHAs with one common merge base verified as an ancestor of both. Checkout and workflow content must match the head. Evaluation uses existing Git access, not GitHub API requests or Buildkite if_changed, and doesn't depend on merge_commit_sha or mergeable. Conflicting and closed pull requests retain these rules. Workflows without path filters don't require diff history.

Matching added, modified, deleted, or type-changed paths and copied destinations are supported. Renames, combined additions and deletions, shallow or missing history, multiple merge bases, unrelated history, identity mismatches, backslashes in paths or patterns, invalid patterns, malformed Git output, more than 3,000 changed files, and more than 2 MiB of Git changed-path output are rejected. The limits don't promise GitHub's file ordering or diff-timeout fallback.

An empty diff or verified nonmatch skips the workflow. An unsupported or inexact filter fails only the affected workflow and never broadens execution. Fetch missing history in the importer; reduce an oversized diff or remove the filter only if its broader execution is acceptable. Identity failures remain errors even when another filter excludes the workflow. Once identity is verified, branch or activity exclusions don't require diff history.

Workflows that don't declare the event skip before event-dependent compilation. A declared event can retain a workflow group even if its branch, tag, base-branch, or activity condition later excludes execution. If every directly runnable workflow excludes the event, upload succeeds with a skipped-only pipeline.

Event identity and fallback

The effective event comes from, in order:

  1. An explicit --event-path snapshot.
  2. The event name attached to linked Buildkite webhook data.
  3. The Buildkite environment fallback.

The fallback prefers GITHUB_EVENT_NAME. It preserves push, pull_request, workflow_dispatch, and schedule from BUILDKITE_GITHUB_EVENT; preserving issues or issue_comment also requires complete pipeline trigger workflow identity. Otherwise pull request builds use pull_request, scheduled builds use schedule, and all other sources, including ui, api, and trigger_job, use push. The fallback can't infer merge-group, release, issue, or comment payloads.

Explicit snapshots don't consult contradictory live event fields. Pipeline trigger events require matching repository, workflow path, immutable ref and SHA, and event-specific identity. Review, release, deployment, merge-group, and default-branch repository events require the original linked payload; rebuilds without it fail rather than inventing an event. A payload or workflow mismatch also fails. These checks don't grant tokens or secrets.

Workflow names

name supplies workflow labels and github.workflow. run-name accepts compile-time github, inputs, and repository or organization vars expressions. A nonempty result changes only the workflow group label, not check identity, the importer label, or the build message:

name: Deploy
run-name: Deploy ${{ inputs.target || 'default' }} by @${{ github.actor }}
on:
  workflow_dispatch:
    inputs:
      target:
        type: string
        default: staging

Omitted or blank run names keep the static label. On non-dispatch events, declared dispatch inputs use typed zero values, not dispatch defaults. Missing properties in a known input set are empty; needs-derived inputs remain unresolved until available. Skipped workflows don't synthesize dispatch inputs.

Actions, artifacts, and caching

Action admission doesn't guarantee that arbitrary code inside an action can use every GitHub service. Source, runtime, input, and service limits all apply. Start with action behavior and restrictions below; each native adapter's release inventory follows its usage details.

Action sources and runtimes

Action type Support
Action type Local ./... Support Source and workflow content verified at execution.
Action type Public owner/repo[/path]@ref Support Resolved to an exact commit and source digest.
Action type Private action Support Unsupported, including within a private reusable workflow.
Action type JavaScript Support node16, node20, or node24, with pre/main/post, inputs, outputs, state, and LIFO posts.
Action type Composite Support Supported shell steps and locked local/public children; literal continue-on-error.
Action type Docker Support Verified Dockerfile or public prebuilt-image metadata on Linux only.
Action type Workflow uses: docker://... Support Unsupported.
Action type Top-level action metadata env Support Accepted, no effect. Not evaluated or injected and grants no credentials. Other unknown metadata fields remain errors.

Public refs lock during upload. Anonymous source access can use a shared importer action-source token for public metadata outside the credential repository; codeload and credential-repository requests remain anonymous. Unavailable token issuance falls back to anonymous access. Exact lowercase commit pins need no API ref lookup. Rate limits can delay new lookups; cached refs and exact pins remain usable. Runtime verifies complete action trees.

Local composites can call only local children. Public composites can call local children or other public actions. Every child is locked, with nested local action depth limited to ten.

Local actions must exist when compiled, not be created by an earlier checkout, download, or command. Runtime local action and workflow bytes must match the compiled source; checking out an older workflow can fail even if the action is unchanged. Use a pinned public action or a verified self-repository reference when appropriate.

Managed Node 16.20.2 runs node16 with an end-of-job deprecation warning. node20 and node24 both run managed Node 24.18.0. Other declarations are rejected.

JavaScript pre-if and post-if accept listed condition functions, including hashFiles(), direct workflow inputs, environment, GitHub fields, service context, matrix, runner, steps, and event-rooted dynamic access. job.status requires v0.103.0. Other whole/dynamic contexts are errors. Empty lifecycle conditions always run without an implicit success guard. Preparation hooks use preparation-time status/environment; posts use final status/environment, including GITHUB_ENV, and root posts see final workflow step state. Nested composites retain isolated steps. Posts run LIFO, and cancellation stays distinct from failure.

Prebuilt images are declared in action metadata:

action.yml
name: Check formatting
inputs:
  path:
    default: .
runs:
  using: docker
  image: docker://ghcr.io/example/formatter@sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
  entrypoint: /usr/local/bin/formatter
  args:
    - ${{ inputs.path }}

Images pull anonymously without ambient Docker credentials. Private images are unsupported. Mutable tags can drift between jobs; use digests. Dockerfile actions require exact runs.image: Dockerfile and can't declare an explicit entrypoint. Prebuilt actions may override entrypoint.

runs.args is an ordered string array with literals or action-input expressions and pure functions. Each item remains one literal argument without shell evaluation. Omitted/empty args preserve image CMD; nonempty args replace it while retaining ENTRYPOINT. Other contexts, credentials, status functions, and hashFiles() are rejected even in unreachable branches. Docker actions can't declare lifecycle hooks, credentials, volumes, arbitrary Docker options, or privileged mode. An argument named --privileged doesn't become a Docker option.

Self-repository actions

uses: $/.github/actions/build loads the action from the repository and exact commit containing the workflow without requiring checkout. $/ selects the repository root. Inside a remote composite it means that action's repository and commit, not the caller's. Ordinary ./ actions remain checkout-relative.

Self references require verification that local workflow bytes match the same path at the event commit; modified source fails, and neither Git HEAD nor workspace contents substitute for identity. Remote reusable workflows provide their own pinned identity. Synthetic validate --event has no source identity; use an exact --event-path snapshot. Self paths can't contain traversal, backslashes, or @ref. Private reusable access doesn't authorize private self actions.

Nested self actions in a remote composite retain its requested github.action_ref. Direct workflow or local-composite self calls expose the containing workflow commit instead. GITHUB_ACTION_REF isn't populated; pass the context through env when needed. Self actions retain ordinary action depth, capability, and access limits.

Checkout action

actions/checkout uses a native adapter, not upstream JavaScript. Checkout remains detached at the workspace root or a fresh nested directory in the event repository. Alternate checkout repositories, GitHub Enterprise Server, persisted credentials, and existing-directory reuse are unsupported. See checkout security.

Inputs must exist in the selected commit's contract before the following restrictions apply:

Input Supported values
Input repository Supported values Omitted or the event owner/repo.
Input ref Supported values Omitted/empty, lowercase 40-hex commit, or static branch in the event repository. Direct github.sha or needs-output expressions must resolve to the exact event SHA. Tags and other dynamic commits are unsupported.
Input token Supported values Omitted only.
Input ssh-key, ssh-known-hosts Supported values Omitted or empty.
Input ssh-strict, set-safe-directory Supported values Omitted or true.
Input ssh-user Supported values Omitted or git.
Input persist-credentials, allow-unsafe-pr-checkout Supported values Omitted or false.
Input path Supported values Omitted/empty or a clean workspace-relative directory without .git segments or symlink-parent traversal.
Input clean Supported values Omitted, true, or false; root workspace must be empty or nested target absent. Existing-directory reuse is unsupported even with false.
Input filter Supported values Omitted/empty or one partial-clone filter without controls.
Input sparse-checkout Supported values Omitted/empty or at most 1,000 nonempty patterns totaling 1 MiB.
Input sparse-checkout-cone-mode, fetch-tags, show-progress Supported values Omitted, true, or false.
Input fetch-depth Supported values Omitted or nonnegative integer; zero means full history. Historical runner-plugin commits default to full history.
Input lfs Supported values Omitted, true, or false; true requires installed Git LFS.
Input submodules Supported values Omitted, false, true, or recursive, trimmed and case-insensitive.
Input github-server-url Supported values Omitted/empty or https://github.com.

Authorized repository-provider Git credentials are scoped to fetch, LFS, and submodule commands and never persisted; otherwise access is anonymous. Statically false job conditions can bypass checkout input restrictions, but step/runtime conditions don't hide invalid adapter inputs.

Submodules use native Git depth and relative URLs: true selects direct children, recursive selects nested children. Public/private GitHub submodules use job-authorized access. External HTTPS is anonymous; git@github.com: rewrites to HTTPS. Other SSH and non-HTTPS transports are unsupported.

Sparse checkout defaults to blob:none unless a filter is explicit. Cone mode selects directories; non-cone mode uses Git ignore patterns. LFS installs repository-local filters without push/locking hooks and downloads selected materialized paths.

steps:
  - uses: actions/checkout@v7
    with:
      path: sources/application
      filter: blob:limit=1m
      fetch-depth: 20
      show-progress: false
  - uses: actions/checkout@v7
    with:
      path: sources/docs
      sparse-checkout: |
        docs
        schemas

Checkout release contracts

Frozen historical release/development commits across v1โ€“v7 retain their exact declared inputs, outputs, and history defaults. These principal releases are supported:

Release Immutable commit
Release v1.2.0 Immutable commit 50fbc622fc4ef5163becd7fab6573eac35f8462e
Release v2.8.0 Immutable commit 0717577d45739eb3c851188b29f50ed6c0b2194e
Release v3.7.0 Immutable commit a37ce9120846195fa4ece8f58b268e6043cb2f26
Release v4 Immutable commit 11d5960a326750d5838078e36cf38b85af677262
Release v5 Immutable commit fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09
Release v6 Immutable commit d23441a48e516b6c34aea4fa41551a30e30af803
Release v7.0.0 snapshot pin Immutable commit 9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0
Release v7.0.1 Immutable commit 3d3c42e5aac5ba805825da76410c181273ba90b1

Other valid resolved immutable commits use the stable v7.0.1 contract and emit W_CHECKOUT_UNKNOWN_COMMIT_FALLBACK. That contract can differ from the requested upstream manifest; pin a listed commit for a known contract. Known historical commits never use fallback. Snapshots refresh with runtime releases. v1.2.0 and v2.8.0 warn with W_CHECKOUT_LEGACY_RELEASE; v1 fetches full history by default and neither exposes ref or commit outputs. Those outputs require a declaring contract, introduced upstream in v4.2.0; fallback exposes both.

Upload artifact action

actions/upload-artifact uses a native ZIP adapter. Frozen historical commits retain declared inputs/outputs, hidden-file defaults, and v1 path behavior.

Input Supported values when declared by the contract
Input name Supported values when declared by the contract Required by v1; later contracts default to artifact.
Input path Supported values when declared by the contract Required. v1 accepts one literal file/directory; later contracts accept literals and bounded *, ?, character classes, and ** globs.
Input if-no-files-found Supported values when declared by the contract warn, error, or ignore. v1 fails on a missing literal and uploads an existing empty directory.
Input retention-days Supported values when declared by the contract Nonnegative integer; advisory only.
Input compression-level Supported values when declared by the contract Zero through nine.
Input overwrite Supported values when declared by the contract Omitted or false.
Input include-hidden-files Supported values when declared by the contract GitHub Actions Boolean; default false. Earlier contracts without the input retain hidden paths.
Input archive Supported values when declared by the contract Omitted or true.

Native jobs accept workspace-relative or absolute platform paths, including Windows drives. Absolute globs archive relative to their literal search root; multiple selections use their least common ancestor. Job-container uploads require workspace-relative paths, even if an absolute container path would exist. Host jobs with services retain native path support.

At most 32 path roots, 10,000 files per artifact, and 64 artifacts per job are supported. Source and ZIP bytes have no separate runtime limit; agent/storage limits apply. Exclusions, symlinks, traversal, UNC and drive-relative paths, alternate data streams, braces, extglobs, leading glob comments, special files, and cross-volume Windows selections are unsupported. Hidden segments are excluded unless enabled by the selected contract.

artifact-id and artifact-digest exist only when declared by the contract; early versions can omit later inputs/outputs. artifact-url is empty because there is no GitHub run URL. Merge, raw upload, overwrite, and effective retention control are unsupported. Downloads verify archive size, digest, members, and filesystem safety before extraction, without a separate fixed expansion-byte policy.

Upload artifact release contracts

Principal releases are:

Release Immutable commit
Release v1.0.0 Immutable commit 3446296876d12d4e3a0f3145a3c87e67bf0a16b5
Release v2.3.1 Immutable commit 82c141cc518b40d92cc801eee768e7aafc9c2fa2
Release v3.2.1 Immutable commit ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5
Release v4.6.0 Immutable commit 65c4c4a1ddee5b72f698fdd19549f0f0fb45cf08
Release v4.6.2 Immutable commit ea165f8d65b6e75b540449e92b4886f43607fa02
Release v5.0.0 Immutable commit 330a01c490aca151604b8cf639adc76d48f6c5d4
Release v6.0.0 Immutable commit b7c566a772e6b6bfb58ed0dc250532a479d7789f
Release v7.0.1 Immutable commit 043fb46d1a93c77aae656e7c1c64a875d1fc6a0a

Unknown valid resolved commits use stable v7.0.1 with W_UPLOAD_ARTIFACT_UNKNOWN_COMMIT_FALLBACK; the manifest can differ from upstream, but adapter limits don't widen and upstream JavaScript doesn't run. Malformed commits are rejected. Principal v1โ€“v3 versions warn with W_UPLOAD_ARTIFACT_LEGACY_RELEASE. v3.2.2 and v3.2.2-node20 remain unsupported GitHub Enterprise Server-only backports. Snapshot refreshes follow runtime updates.

Download artifact action

actions/download-artifact uses producer-bound native ZIP downloads. Artifacts must come from verified direct needs producers and the supported upload adapter.

Input Supported values
Input name Supported values Exact unique name, mutually exclusive with pattern; runtime expressions allowed.
Input pattern Supported values Bounded name glob with required merge-multiple: true; runtime expressions allowed.
Input path Supported values Optional literal workspace-relative destination.
Input merge-multiple Supported values Omitted/false with name; true with pattern.
Input v8 skip-decompress Supported values Omitted or false.
Input v8 digest-mismatch Supported values Omitted or error.

Patterns can select at most 64 distinct names using *, ?, character classes, **, or one leading nonnested alternation of two through eight literal prefixes with a glob suffix, such as {backend,product}-*. Other brace expansion is unsupported. Case-insensitive duplicate artifact names are rejected.

All selected archives together may contain at most 10,000 files. Every archive is validated and staged before the destination changes. Exact member-path collisions are resolved deterministically: the later artifact by name wins. Digest or ZIP failure is fatal. download-path is supported. Artifact-ID, all-artifact, cross-run/repository, raw, REST, and non-merged pattern modes are unsupported.

Download artifact release contracts

These root action revisions use the adapter:

Release Immutable commit
Release v4.3.0 Immutable commit d3f86a106a0bac45b974a628896c90dbdf5c8093
Release v5.0.0 Immutable commit 634f93cb2916e3fdff6788551b99b062d0335ce0
Release v6.0.0 Immutable commit 018cc2cf5baa6db3ef3c5f8a56943fffe632ef53
Release v7.0.0 Immutable commit 37930b1c2abaa49bbe596cd826c3c89aef350131
Release v8.0.0 Immutable commit 70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3
Release v8.0.1 Immutable commit 3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c

Unknown lowercase 40-hex resolved commits use stable v8.0.1 with W_DOWNLOAD_ARTIFACT_UNKNOWN_COMMIT_FALLBACK. The fallback can differ from upstream inputs but doesn't widen adapter limits. Malformed commits are unsupported.

Cache action

actions/cache, /restore, and /save run admitted stock cache-v2 JavaScript clients against the Buildkite Results service. Root restore/post-save and separate entry points are supported, with tar and zstd-or-gzip versioning.

Workflow/job cache-mode supports the following best-effort client behavior; jobs override workflow values. Reusable calls and workflows declaring workflow_call don't support this key.

Mode Restore Save
Mode read Restore Allowed Save Skipped
Mode write Restore Allowed Save Allowed
Mode write-only Restore Skipped Save Allowed
Mode none Restore Skipped Save Skipped

The effective value overrides action/workflow ACTIONS_CACHE_MODE in JavaScript lifecycle invocations, not Docker actions. Omission preserves client behavior; there is no trigger-based default. Clients must honor the variable: the known 6.2.0 client bundle does, while listed v6.1.0 and older clients ignore it. Skipped restores are misses and can still fail with fail-on-cache-miss: true.

Cache mode isn't authorization

Cache mode doesn't change cache-token issuance or provenance-based server authorization. Clients or direct requests that ignore it remain governed by the server, not the requested mode.

Compatible JavaScript and Docker actions can receive job-bound cache credentials when available; ordinary shell steps and native adapters don't. Root actions/setup-node, setup-java, setup-python, setup-go, and setup-dotnet invocations can receive a subprocess-only synthetic GITHUB_SERVER_URL to select cache v2. Workflow expressions retain the real provider URL. This is a repository-level allowlist, not a guarantee that every accepted revision or bundled dependency has been individually audited.

Windows cache tools use a restricted path containing Git's usr\bin and zstd under Program Files, then System32. Install Git for Windows and zstd there for GNU tar/zstd. From v0.105.3, the runtime also accepts tools\zstd\zstd.exe on the system volume only for the official zstd 1.5.7 Windows x64 binary with SHA-256 8076aae03feac7c66b319579e82172eed168deed2a3f25e5e2d3c60f55e84111. Each lifecycle invocation uses a temporary verified copy, not that writable directory or its siblings. No download/install occurs; missing or different bytes don't add the tool.

Without admitted zstd, clients use gzip; without Git tar, they can use System32 tar. Cross-OS archives require GNU tar and zstd, but cross-OS or cross-worker persistence isn't guaranteed by this reference. Compression changes can cause one-time misses. Workflow paths, GITHUB_PATH, and package-manager shims don't select these tools. Keep Program Files outside workflow write access. Tool selection isn't a sandbox against administrators or processes sharing the runner account and doesn't enable Windows cache volumes.

Cache release contracts

Known compatible historical development/release snapshots include these releases:

Major Principal admitted releases Exact recommended pin
Major v3 Principal admitted releases 3.4.0, 3.4.2, 3.4.3, 3.5.0 Exact recommended pin 6f8efc29b200d32929f49075959781ed54ec270c (v3.5.0)
Major v4 Principal admitted releases 4.2.0, 4.2.1, 4.2.2, 4.2.3, 4.2.4, 4.3.0 Exact recommended pin 0057852bfaa89a56745cba8c7296529d2fc39830 (v4.3.0)
Major v5 Principal admitted releases 5.0.0, 5.0.1, 5.0.2, 5.0.3, 5.0.4, 5.0.5, 5.1.0 Exact recommended pin caa296126883cff596d87d8935842f9db880ef25 (v5.1.0)
Major v6 Principal admitted releases 6.0.0, 6.1.0 Exact recommended pin 55cc8345863c7cc4c66a329aec7e433d2d1c52a9 (v6.1.0)

Versions before v3.4.0/v4.2.0 use unsupported cache-v1 clients. Withdrawn v3.4.1 is excluded because upstream published it with an incorrect SHA. v3 uses managed Node 16 with a deprecation warning; Node 20 declarations use Node 24.

Unknown resolved cache commits don't run directly. Compilation substitutes the newest principal release for the requested admitted major, or v6.1.0 for an unrecognized major, branch, or bare commit. For example, v5.2.0 substitutes v5.1.0. W_CACHE_UNKNOWN_COMMIT_SUBSTITUTED names both commits and a pin to remove the warning. The substitute must resolve to its recorded commit or compilation fails. This includes floating refs, pre-v2 clients, withdrawn versions, and unknown exact pins. Pin a listed commit to avoid substitution. Snapshot refreshes follow runtime releases.

Credentials and deployments

Repository access, workflow tokens, ordinary secrets, cache credentials, and OIDC are separate boundaries. An event payload or repository file doesn't grant authority. Check each boundary and the deployment protection differences before running trusted workflows.

Repositories

Public GitHub event repositories are supported. Private GitHub repositories require authorized repository-provider Git credentials, available to jobs on Buildkite hosted agents. Internal/private Origin repositories require those credentials and the exact pipeline BUILDKITE_REPO URL https://origin.cursor.com/git/<namespace>/<repository>.git. GitHub Enterprise Server and unlisted providers are unsupported.

Alternate checkout repositories and private actions are unsupported. Approved private reusable sources are importer-only; see reusable access. Public actions remain subject to their source and runtime limits.

GitHub token

A job requests one short-lived event-repository token when it statically references secrets.GITHUB_TOKEN, workflow-authored github.token, or an effective action input default that can reach github.token for that provider. A provider guard can exclude GitHub token use for Origin. Native adapters ignore upstream defaults, so checkout alone doesn't request it. Tokens aren't automatically placed in the initial environment.

Public support is limited to running command jobs on Buildkite hosted agents in github.com pipelines using the full-access GitHub repository provider. The pipeline's Allow workflow-authorized GitHub access tokens setting must be enabled. Existing pipelines default off; pipelines created with GitHub Actions setup mode default on.

The immutable top-level workflow must be a simple .yml or .yaml directly under .github/workflows/. Its permission policy applies to every receiving job, including reusable jobs. Pull requests and their triggered/rebuilt descendants have a contents: read ceiling. Merge queues and descendants can't request workflow tokens. Backend provenance checks remain authoritative.

permissions:
  contents: read
jobs:
  inspect:
    runs-on: ubuntu-latest
    steps:
      - env:
          GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        run: gh api repos/{owner}/{repo}

Follow the actual rejection diagnostic. A disabled-setting error links to the pipeline repository setting. From v0.104.0 with a compatible backend, known denial reasons identify who can act; older servers retain generic guidance. Permission or App-access errors can require checking top-level permissions and the Buildkite GitHub App's repository access; these checks aren't a universal fix for provenance, policy, rate-limit, or service failures. For an unexplained rejection, send support the diagnostic and build URL.

From v0.102.1, acquisition retries Agent API 503 responses and 429 responses with valid Retry-After, at most three attempts in 45 seconds with 15-second attempt limits. Cancellation and earlier job deadlines apply. Delays use one- and two-second bases plus bounded jitter, respecting Retry-After seconds or dates without exceeding the remaining budget. Other statuses, transport failures, and invalid success responses aren't retried. Action-source requests and checkout don't use this retry policy. Every attempt remains authorized and counts against the server's request limit.

Use write tokens only with trusted code

Job binding doesn't prove an arbitrary commit is trusted. Restrict who can create builds and enable write access only when eligible branch builds run trusted code. Pull request and merge-queue restrictions don't replace that policy.

Permissions

Repository permissions apply when a job references a workflow token, including an effective action input default that reaches github.token. Declaring permissions alone doesn't request a token.

permissions:
  contents: read
  pull-requests: write

Supported repository names are actions, artifact-metadata, attestations, checks, contents, deployments, discussions, issues, packages, pages, pull-requests, security-events, and statuses, with read, write, or none. The separate id-token permission controls OIDC.

Top-level declaration Token scope
Top-level declaration Omitted Token scope Exactly contents: read; GitHub repository and organization defaults aren't inherited.
Top-level declaration Explicit map Token scope Requested supported permissions. Write access requires this form.
Top-level declaration read-all Token scope CLI expansion to read access for the supported repository names above, excluding id-token, models, repository-projects, code-quality, metadata, and vulnerability-alerts.
Top-level declaration Empty map or all none Token scope No token. A job that requests one fails compilation rather than receiving an empty token.
Top-level declaration write-all Token scope Accepted by the compiler but rejected for hosted token issuance.

Every receiving job uses the immutable top-level requesting workflow's repository policy. Job maps and called-workflow maps can't narrow or expand it; compilation warns about differences or ineffective narrowing. You can't give individual jobs different repository scopes. The separate id-token policy retains job-level behavior and called-workflow narrowing.

Other secrets and OIDC

Direct jobs support static ${{ secrets.NAME }}. Local reusable calls can inherit or map names. Runtime retrieves destination-job Buildkite secrets and registers redaction before use. Missing or denied secrets fail without printing values or the agent error; diagnostics distinguish migration/access issues from temporary service failures. See Migrate GitHub Actions secrets.

These aren't GitHub repository, environment, event, or fork-scoped secrets. Buildkite secret access policies authorize the destination job, whose code can also call buildkite-agent secret get. Environment-prefixed names remain ordinary Buildkite secrets. GITHUB_TOKEN is separate and can't be replaced with an ordinary secret.

Dynamic, whole-context, filtered, or projected secret access; conditions/compile-time secret use; and remote secret forwarding are unsupported. Explicit reusable mappings must be direct secret references, not literals or values derived through other contexts. Action metadata can't add secret authority; a secret used only by an optional action input is empty unless another field requires it.

id-token: write exposes getIDToken() to host JavaScript actions, including composite children. It returns a Buildkite OIDC token for the requested audience, not GitHub issuer/claims. read, none, or omission doesn't expose the endpoint. Cloud trust configuration must use the Buildkite issuer and claims. See AWS setup.

Tokens use the default Buildkite claims and subject without plugin configuration. Native pipeline setup doesn't expose custom claims, AWS session tags, or subject overrides. Contact the Buildkite Support team for custom OIDC requirements. Configuration doesn't grant id-token: write.

Endpoint variables are lifecycle-invocation scoped: shell steps, Docker actions, and actions inside job containers don't receive them. Container getIDToken() fails with a missing-endpoint diagnostic.

Deployment environments

Literal environment names are supported on top-level workflow jobs, with optional url. Expression names and reusable-workflow job environments are rejected. Inside a Buildkite job, upload and compile resolve GitHub.com protection rules, secret names, and variables through the job-scoped Agent API. Only configuration and names reach the importer, not GitHub credentials or secret values. The pipeline repository is the only source.

Resolution fails closed, including outside a Buildkite job or on GitHub Enterprise Server. One upload batches distinct environments, at most 20 per request, subject to backend per-job and per-installation hourly budgets. Backend rejection fails compilation; there is no unprotected fallback or user-supplied GitHub token option.

Environment feature Behavior
Environment feature Required reviewers Behavior One block step per workflow/environment. Any user who can unblock the pipeline can approve; GitHub reviewer lists, prevent_self_review, and administrator bypass aren't enforced.
Environment feature Secret names Behavior Environment-defined names resolve to prefixed Buildkite secrets as described below. Other names remain unchanged.
Environment feature Variables Behavior Available in runtime fields, not job conditions or compile-time fields; see variable scopes.
Environment feature Wait timers, deployment branch policies, custom protection rules Behavior Rejected during compilation.
Environment feature environment.url, GitHub deployments and statuses Behavior Accepted, no effect; no deployment records are created.

Environment production secret DEPLOY_KEY uses Buildkite secret PRODUCTION_DEPLOY_KEY. Uppercase the environment name, replace characters outside A-Z, 0-9, and _ with _, and prefix _ when it starts with a digit. For example, 1st produces _1ST_DEPLOY_KEY. Compilation rejects keys over 255 characters or beginning with BK or BUILDKITE, and distinct environments that produce the same prefix. Rename the environment or secret to remove the conflict. Environment names that differ only in case identify the same environment. Buildkite secret access policies govern values.

Approval gates use running block state so unrelated jobs continue. A gate appears even if its job condition later skips. Matrix instances share one gate. Gated jobs can't be retried manually; create a new build for fresh approval. Configuration changes apply at the next compile. Buildkite OIDC tokens don't add a GitHub environment claim.

GitHub services

No general GitHub Packages, Releases, Checks, deployments, or GitHub artifact API emulation is provided beyond the integrations above. An admitted action can still fail if it requires an unsupported service.

Workflow syntax reference

The following sections cover workflow-level configuration, then jobs, steps, and expressions and contexts. Repository permissions and cache mode are described with their credential and action behavior.

Reusable workflows

Calls support local ./.github/workflows/... files, verified self-repository $/.github/workflows/... files, and literal public or approved private owner/repository/.github/workflows/file.yml@ref references. Remote files must be .yml or .yaml directly under .github/workflows. References resolve to a pinned commit for the operation; nested local paths stay in that repository. Dynamic paths are unsupported. Nesting is limited to four levels.

Private source access is separate from workflow-token permission and disabled by default in native GitHub Actions pipelines. Contact the Buildkite Support team before relying on private reusable workflows. Advanced custom importers can enable it with the CLI upload option.

When enabled, access covers the pipeline repository and cross-repository sources available to the importer's existing Git credentials. Repository-provider credential access is approved per repository. Git access can also resolve public sources after anonymous API limits. Missing and denied repositories, refs, and paths use the same error. Private reusable access doesn't enable private actions or forward importer credentials into jobs. Access is repository-wide, not workflow-path scoped; restrict importer credentials to approved repositories. See reusable-source credential boundaries.

Interface Supported behavior
Interface Inputs Supported behavior boolean, number, and string; static caller values and supported compile-time expressions. Defaults are literals or compile-time github and vars expressions, not references to inputs.
Interface Needs-derived string inputs Supported behavior Direct declared needs.<job>.outputs.<name> references, templates, or pure expressions whose other parts resolve before execution, including github, vars, matrix, and static parent inputs. Runtime-only values such as github.run_id can't be mixed in.
Interface Needs-derived Boolean or number inputs Supported behavior One expression returning the declared type, such as fromJSON(needs.detect.outputs.count) or needs.detect.outputs.deps == 'true'. A raw output string isn't a Boolean or number.
Interface Nested deferred input forwarding Supported behavior Exactly ${{ inputs.name }} with the same declared type. Combining it with other text is unsupported.
Interface Outputs Supported behavior Direct mappings from jobs.<job>.outputs.<name>. Literal or compound output expressions are unsupported.
Interface Call matrix Supported behavior Static matrices, including composition with static callee matrices. Dynamic call matrices are unsupported.
Interface Secrets Supported behavior Repository-local inheritance or explicit mappings as described below. Public and private remote forwarding is unsupported.
Interface Call result Supported behavior Aggregate result visible to the caller.

Every needs-derived input must name a declared direct dependency. needs.<job>.result and whole output objects aren't supported as input values. A call's scheduling dependencies don't appear in the callee's needs context: called jobs see only their own declared dependencies.

.github/workflows/build.yml
on:
  workflow_call:
    inputs:
      target:
        type: string
        required: true
    outputs:
      image:
        value: ${{ jobs.build.outputs.image }}
jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      image: ${{ steps.image.outputs.value }}
    steps:
      - id: image
        run: echo "value=app-${{ inputs.target }}" >> "$GITHUB_OUTPUT"
jobs:
  prepare:
    runs-on: ubuntu-latest
    outputs:
      target: ${{ steps.target.outputs.value }}
    steps:
      - id: target
        run: echo 'value=production' >> "$GITHUB_OUTPUT"
  call:
    needs: prepare
    uses: ./.github/workflows/build.yml
    with:
      target: ${{ needs.prepare.outputs.target }}

Calls support if over caller github, inputs, vars, direct needs, and status functions, with the implicit success guard. Conditions run before call-matrix expansion and can't use matrix, strategy, callee contexts, steps, env, runner, or secrets. False calls skip all descendants, including jobs with always(), and expose skipped and empty outputs. Nested conditions apply outer to inner; callee results don't change an outer condition. An implicit success check doesn't require unambiguous matrix output values.

Local calls can use secrets: inherit or map declared aliases to one direct ${{ secrets.NAME }} or ${{ secrets['NAME'] }} reference. Required aliases need a mapping; unmapped optional aliases are empty. Every nested edge must explicitly inherit or map again. Omission removes ordinary secret access below that edge; it doesn't affect direct caller jobs or GITHUB_TOKEN. Nested mappings retain the original Buildkite secret name, and a missing mapping can't recover a same-named secret. Literal, compound, dynamic, and non-secret mapping values are unsupported.

Verified $/ calls within the root repository retain local secret-forwarding scope through nested $/ and ./ calls. Explicit owner/repository/...@ref calls leave that scope, even when they name the same repository and commit; nested calls can't restore it. ${{ secrets.GITHUB_TOKEN }} can map to a declared alias but remains a workflow token, not an ordinary secret.

Environment and defaults

Workflow, job, and step env maps use most-specific-wins precedence. Individual values support field-specific expressions; an entire map can't be expression-valued. Workflow and job defaults.run.shell and defaults.run.working-directory set run defaults, subject to shell and path restrictions. Windows defaults to pwsh, other host jobs to bash, and job containers to sh.

env:
  GOFLAGS: -mod=readonly
defaults:
  run:
    shell: bash
    working-directory: ./src
jobs:
  test:
    runs-on: ubuntu-latest
    env:
      GOFLAGS: -race
    steps:
      - run: go test ./...

Concurrency

Static groups become repository-scoped, case-insensitive Buildkite Pipelines concurrency groups. They support compile-time github, vars, static reusable inputs, supported operators and pure functions, and job-level concrete matrix and strategy values. Ordinary runtime needs groups are unsupported; matrix-producer scheduling is a bounded exception.

From v0.106.0, workflow-level concurrency during hosted upload also accepts github.run_id, such as group: codeql-${{ github.run_id }}. This uses the owning Buildkite build ID: repeated imports in one build share a group, and different builds get different groups. Offline validation and compilation need a Buildkite build identity. This doesn't admit run identity in other compile-time fields, or admit github.run_number or github.run_attempt in concurrency.

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: ${{ startsWith(github.ref, 'refs/pull/') }}

Buildkite Pipelines queues every waiting entry rather than replacing GitHub's pending entry. queue is unsupported. Workflow-level cancel-in-progress: true, including a true expression, warns and leaves superseded builds running. Job-level cancel-in-progress is unsupported.

Called workflows retain workflow-level concurrency around all their jobs, including static call-matrix instances and nested calls. Jobs inside a call remain parallel except for their own dependencies and job groups. Reusing an enclosing workflow group in a job or nested call is unsupported.

Calls with needs require prerequisites with no concurrency or provably disjoint groups. Shared or unresolved groups and cycles involving dependencies and ordered queues are rejected, including transitive and matrix prerequisites. This check covers the generated pipeline, not other pipelines or later manual uploads.

The call's gate waits for all external prerequisites, including failed and skipped ones, but reserves its queue position at upload, earlier than GitHub. Later calls can wait behind unfinished prerequisites; W_REUSABLE_WORKFLOW_CONCURRENCY_QUEUED_BEFORE_PREREQUISITES reports this difference. A statically false call omits its gate. A runtime-dependent call still enters and holds the group until its jobs finish, even if it skips; W_REUSABLE_WORKFLOW_CONCURRENCY_ENTERED_BEFORE_CALL_CONDITION reports this behavior.

Workflow concurrency can't enclose or depend on deferred expansion as described under matrices from outputs. Cancel the whole build, not an individual job, when a workflow gate is active.

Leave Cancel Intermediate Builds and Skip Intermediate Builds disabled for server-side dispatch. These settings work by branch, not workflow concurrency group, and can cancel or skip sibling workflow builds from the same event.

Job syntax

Jobs support static dependencies, outputs, runner selection, matrices, configuration variables, environments, and Linux containers within the following limits.

Job configuration

Key Supported behavior
Key name Supported behavior Static github, reusable inputs, and concrete matrix and scalar strategy values.
Key needs Supported behavior One static job ID or an array. Matrix fan-out and fan-in are automatic.
Key runs-on Supported behavior Accepted label or label array, including concrete matrix/strategy values or the bounded output-derived form. Labels match case-insensitively.
Key if Supported behavior Evaluated before the job starts; see conditions.
Key outputs Supported behavior Output expressions for downstream needs. Ambiguous matrix values fail rather than selecting a winner. See size limits.
Key env, defaults.run Supported behavior Normal precedence and field-specific expressions.
Key timeout-minutes Supported behavior Literal number, at most 360. Expressions are rejected.
Key continue-on-error Supported behavior Boolean literal or Boolean-valued expression. Tolerated failures remain visible as soft failures but report downstream success.
Key environment Supported behavior Literal top-level-job environment, resolved during compilation.
Key snapshot Supported behavior Accepted, no effect. Custom image creation isn't implemented.
jobs:
  build:
    runs-on: ubuntu-latest
    outputs:
      image: ${{ steps.image.outputs.value }}
    steps:
      - id: image
        run: echo 'value=app:latest' >> "$GITHUB_OUTPUT"
  deploy:
    needs: build
    if: github.ref == 'refs/heads/main' && success()
    runs-on: ubuntu-latest
    steps:
      - env:
          IMAGE: ${{ needs.build.outputs.image }}
        run: echo "$IMAGE"

Results and outputs come from verified producer results. Retrying one producer can make selection ambiguous; retry the whole build. A tolerated job failure stops ordinary steps, runs eligible failure/always steps and post-actions, publishes outputs, and reports success through needs. It soft-fails with reserved exit status 78. Whole-job timeout remains cancelled and isn't tolerated.

See queue selection and mappings for runner setup and deployment environments for protection rules, approvals, and secret naming.

Runners from job outputs

A non-matrix job can select a runner from one declared output of a prerequisite with exactly one static instance:

jobs:
  plan:
    runs-on: ubuntu-latest
    outputs:
      runner: ${{ steps.select.outputs.runner }}
    steps:
      - id: select
        run: echo 'runner=["ubuntu-22.04"]' >> "$GITHUB_OUTPUT"
  build:
    needs: plan
    runs-on: ${{ fromJSON(needs.plan.outputs.runner) }}
    steps:
      - run: echo ready

A direct output can supply one label. Templates, label arrays, and supported pure functions also work, but every reference, even in an unselected branch, must name that same producer output. A reusable string input can forward the value unchanged without adding the producer to the callee's needs context.

The deferred runs-on step waits for the producer and uploads the consumer and its transitive dependants. W_RUNS_ON_DEFERRED identifies this behavior. The output is limited to 1 KiB and supplies scheduling data only: normal runner mapping, platform, admission, and credential rules still apply. Missing outputs, invalid label types, or rejected targets fail before those jobs upload.

The deferred matrix rules also govern committed source, snapshots, graph budgets, retries, and skipped producers. Independent runner and matrix components can coexist, but a component with dynamic runner selection can't join or start another deferred stage. Its consumer can't also have a matrix. Root workflow concurrency, or called-workflow concurrency enclosing or depending on deferred work, is unsupported. This doesn't enable dynamic environments or general output-derived concurrency.

Use validate or compile --format ir-json to inspect the graph; compile --format pipeline can't emit it. See deferred upload setup.

Repository and organization variables

${{ vars.NAME }} reads configuration, not secrets. Within a Buildkite job, applicable workflows, reusable calls, and action input defaults that reference vars trigger one repository/organization lookup per upload. Providers other than GitHub.com don't make that request. The backend uses its own repository-restricted credentials.

Position Precedence
Position Job and reusable-call if Precedence Repository over organization; no environment scope. Check environment values in a step condition.
Position Job env, run defaults, outputs, service env and credentials, step fields, action input defaults Precedence Environment over repository over organization.
Position Compile-time run-name, runners, strategy, concurrency, job names, container images, reusable inputs Precedence Repository over organization; environment names remain literal.

These are the runtime's supported scopes; don't assume environment values affect pre-job selection. Names match case-insensitively. Missing names are empty strings, including in compile-time fallbacks. Dynamic indexing, projections, and toJSON(vars) use the same scope. Matrix instances share their job's scope; reusable jobs don't have environment scope.

The lookup accepts at most 500 repository and 1,000 organization names, 48 KiB per value, and 256 KiB combined. Rate limits, including ten requests per job per hour, and outages fail workflows using vars; other workflows can still upload. E_VARIABLE_RESOLUTION retains specific backend errors and any retry delay. A generic resolution error needs support investigation, not an assumed installation or permission fix.

A missing endpoint or organization opt-out returns empty scopes on 404. Outside a Buildkite job, runtime references are empty and compile-time references fail for lack of a source. This differs from declared environments, which fail closed.

jobs:
  deploy:
    runs-on: ${{ vars.RUNNER || 'ubuntu-latest' }}
    if: vars.DEPLOY_ENABLED == 'true'
    environment: production
    env:
      AWS_REGION: ${{ vars.AWS_REGION }}
    steps:
      - if: vars.TIER == 'gold'
        run: echo "$AWS_REGION"

Variables don't reduce token or secret requests: conditions using vars remain runtime decisions. Values are stored in job artifacts and printed by compile --format ir-json. Compile-time uses can also appear in labels, pipeline YAML, or runner diagnostics. Don't store credentials in variables. See variable visibility.

Matrix strategies

Static matrices support include, exclude, and definitions derived from compile-time github, event data, reusable inputs, and fromJSON. Whole dimensions and include/exclude arrays must resolve during compilation, except for the bounded output-derived forms below. A job can expand to 256 instances.

strategy:
  max-parallel: 2
  matrix:
    go:
      - "1.25"
      - "1.26"
    os:
      - ubuntu-22.04
      - ubuntu-24.04
    exclude:
      - go: "1.25"
        os: ubuntu-24.04

Ordinary job matrices accept literal max-parallel; matrix-producer outputs provide a limited dynamic form. Call matrices with multiple instances can't use max-parallel, because invocation-level parallelism can't be preserved. Static call and callee matrices compose, including nested calls.

fail-fast is accepted but has no cancellation effect. Scalar strategy properties report job-index (zero-based final expansion order), job-total, configured fail-fast (default true), and max-parallel (default final row count). A non-matrix job uses index zero, total one, and parallel limit one. Reporting fail-fast: true doesn't enable it.

Matrices from job outputs

Exactly fromJSON(needs.<job>.outputs.<name>) can supply a whole matrix or its whole include array after a prerequisite runs:

jobs:
  plan:
    runs-on: ubuntu-latest
    outputs:
      matrix: ${{ steps.plan.outputs.matrix }}
    steps:
      - id: plan
        run: echo 'matrix=[{"target":"amd64","runner":"ubuntu-latest"}]' >> "$GITHUB_OUTPUT"
  build:
    needs: plan
    runs-on: ${{ matrix.runner }}
    strategy:
      matrix:
        include: ${{ fromJSON(needs.plan.outputs.matrix) }}
    steps:
      - run: ./build "${{ matrix.target }}"
  publish:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - run: ./publish

Upload creates the producer and a deferred :github: matrix ยท build step. After the producer succeeds, that step expands and uploads the matrix and its downstream jobs. W_MATRIX_DEFERRED explains the delay. validate and compile --format ir-json show the graph; pipeline-format compilation can't render deferred uploads. Expanded jobs use ordinary matrix labels, checks, dependencies, runner admission, and credential restrictions.

Independent components expand separately. Components with shared downstream jobs merge so each job uploads once; sharing only a producer doesn't merge them. Matrices can chain when a later producer is itself deferred. Each stage uploads jobs whose rows are known and waits for later producers before adding later stages. Prerequisites outside the deferred downstream graph remain in the initial upload.

The following limits apply to every stage:

  • The producer has exactly one instance and a declared output. It must run on Linux or macOS because its queue also runs the deferred importer. Expanded jobs may use enabled Windows targets.
  • Matrix JSON is at most 64 KiB from v0.102.3. Each nonempty row has at most 64 properties containing strings, finite numbers, or booleans. Strings are at most 1 KiB. Null, nested arrays/objects, ambiguous keys, duplicate instances, zero rows, malformed JSON, and missing outputs fail the deferred step.
  • The 256-instance and 1,024-expanded-job limits still apply. Deferred components share the remaining workflow budget equally after upfront jobs; chained stages consume that same component budget. Exceeding a share fails before upload, even if another component hasn't used its share.
  • The workflow must be tracked inside the repository and match the build commit. Outside, untracked, staged-only, or modified workflow source can't use deferred expansion.
  • Deferred execution uses the original event, variable snapshot, runner mappings, OIDC settings, workflow source, reusable source commits, and locked action revisions. Changed sources or incompatible graph changes fail with instructions to retry the build. Outputs never become workflow source or credential authority.
  • Root workflow concurrency is unsupported. A called workflow with workflow concurrency can't contain a deferred matrix or depend on deferred work; its gate would close before later jobs finish.
  • Generated job, placeholder, continuation, and approval-gate keys must remain unique. Collisions fail before execution. Protected environment jobs retain their shared approval gate through deferred uploads.
  • A failed, skipped, or cancelled producer skips its downstream placeholders, including always() dependants whose matrix is unknown. Healthy branches in a merged component still expand; shared joins skip. Earlier completed stages remain unchanged. Missing or invalid producer results are errors, not skips.
  • Any compilation error fails the whole deferred workflow rather than uploading only its static jobs. Other workflows in an aggregate upload can continue.
  • Retrying a deferred step verifies its previous upload and doesn't duplicate jobs. Retrying a producer after expansion invalidates the earlier result, even if the new output is identical. Retry the whole build instead.

Matrices derived from steps, compound needs expressions, or other dynamic sources remain unsupported. See deferred upload setup for importer and runtime requirements.

Scheduling from matrix-producer outputs

A real output-derived matrix can use other declared outputs of its same ordinary single-instance producer for scheduling:

jobs:
  build:
    needs: plan
    runs-on: ubuntu-latest
    strategy:
      max-parallel: ${{ fromJSON(needs.plan.outputs.parallel) }}
      matrix:
        include: ${{ fromJSON(needs.plan.outputs.matrix) }}
    steps:
      - run: ./build "${{ matrix.target }}"

parallel must be a JSON integer from 1 through 256. The limit applies to this matrix in this build. Alternatively, concurrency: ${{ needs.plan.outputs.group }} supplies a nonempty repository-scoped, case-insensitive group. Templates and pure functions may use that producer's direct output references, but no other expression contexts. All instances in a group run one at a time; max-parallel can't raise that limit. See the complete parallelism and group examples.

The deferred step waits for every upfront workflow job, not only the producer, and holds no concurrency slot. This avoids deadlocks in ordered queues. Missing, ambiguous, or invalid scheduling outputs fail expansion.

This subset requires one matrix and one expansion stage in the component: no joins, chains, projected reusable outputs, or other producers. Static matrices, non-matrix jobs, downstream jobs, and reusable calls can't use it. Independent components without dynamic scheduling keep their ordinary join/chain support. Workflow concurrency, including called-workflow gates anywhere in the workflow, remains unsupported. Cancellation and retry behavior doesn't change.

Containers and services

Linux supports job containers and services. macOS and Windows reject containers, services, Docker actions, and Docker capability. Job-container images must provide sh and run the mounted self-contained Linux runtime. Runner-mapped cache volumes can't be used with job containers.

services:
  postgres:
    image: postgres:16
    env:
      POSTGRES_PASSWORD: test
    ports:
      - 5432
    options: >-
      --health-cmd pg_isready
      --health-interval 2s
      --health-timeout 5s
      --health-retries 10

Job containers accept image, env, ports, volumes, and options. Services also accept credentials, command, and entrypoint.

Field Expressions and behavior
Field Job image Expressions and behavior Compile-time github, inputs, scalar strategy, matrix, and scoped compile-time variables. Null or exactly empty evaluated images select the host without container env or ports. Literal empty or whitespace-only images are invalid. Secrets, needs, step outputs, and whole/dynamic contexts aren't supported.
Field Service fields Expressions and behavior Compile-time contexts and runtime needs outputs, including image fallbacks. Empty evaluated images omit the service.
Field Service env Expressions and behavior Job environment, scoped runtime variables, needs outputs, and named secrets, including combined pure expressions. Evaluated after job env, before standard step variables; sibling service entries aren't visible.
Field Whole service map Expressions and behavior A non-credential map can use fromJSON with needs-output expressions and pure functions. Credentials must remain declared statically.
Field Credentials Expressions and behavior github, runtime vars, named secrets, job env, needs, known matrix, scalar strategy, and compile-known named inputs. Dynamic secret names and direct github.token are rejected; authorized secrets.GITHUB_TOKEN is the supported token spelling.

From v0.104.1, credentials support named inputs.name or inputs['name'] when known during compilation, including typed dispatch values/defaults and static reusable inputs. Computed, whole-context, unknown, and deferred input references are rejected, even in unreachable branches. ${{ secrets[inputs.name] }} remains unsupported even for a known input: use a static secret name. Needs-derived reusable inputs can instead flow through job env, with credentials reading that environment value. Input values derived from variables keep their compile-time snapshot; direct credential vars uses runtime scope.

Service startup doesn't support runner, job, step outputs, or hashFiles(). Named secrets stay required and redacted even in unused fallback branches. Unsupported references fail even in unreachable branches. Credentials use a private per-job Docker configuration, never ambient Docker credentials; passwords go to docker login on standard input. Implicit GHCR authentication isn't supported. Mutable image tags resolve at job start; use a digest for immutability.

Container environment names start with a letter or underscore and otherwise contain letters, digits, underscores, or dots, such as discovery.type. Service IDs are at most 255 ASCII characters, start with a lowercase letter or underscore, and otherwise contain lowercase letters, digits, underscores, hyphens, or dots. Dotted IDs remain distinct from hyphenated IDs; index them with brackets, for example ${{ job.services['s3.docker.test'].ports[6379] }}.

Each job uses a private Docker bridge. Container jobs address services by name; host jobs use published ports, with dynamic host ports when omitted. job.services.<id> exposes id, network, and ports. Health-checked services must become healthy before steps start; others must start. Startup failures include bounded status, health, port, and log diagnostics.

Container setting Limit or restriction
Container setting Job volumes Limit or restriction DESTINATION for anonymous volumes or `SOURCE:DESTINATION[:ro\
Container setting Job options Limit or restriction Passed to docker create, except --network, --net, and --entrypoint, including equals forms. No expressions, line breaks, NUL, or more than 65,536 bytes. Double quotes group arguments; single quotes are literal. No shell evaluation.
Container setting Service options Limit or restriction Passed through except network overrides. May grant privileges, mount host paths, publish ports, or alter resources.
Container setting Services per job Limit or restriction 32
Container setting Service environment Limit or restriction 256 entries per service
Container setting Service ports or volumes Limit or restriction 128 each per service; named, anonymous, and absolute bind volumes supported.

Cleanup emits bounded masked logs and removes job containers, services, networks, private Docker configuration, and attributable volumes. Pre-existing named volumes remain. Job volumes created with custom drivers, or named only through options, can remain when ownership is ambiguous; unrelated volumes aren't deleted to recover them. Remaining owned resources fail the job. Docker isn't a security or resource-isolation boundary: isolate the whole job and enforce host CPU, memory, disk, and network limits.

Step syntax

Steps support commands, supported actions, conditions, environments, and bounded background work.

Step configuration

Key Supported behavior
Key name, id Supported behavior IDs must be unique within a job. Use them for outputs and background controls.
Key if Supported behavior Step status, outputs, environment, and service ports in addition to the listed job-condition contexts.
Key env Supported behavior Overrides job and workflow values; supports field-specific expressions and fallbacks.
Key continue-on-error Supported behavior Boolean literal or Boolean-valued expression. Failure gives outcome: failure, conclusion: success, then continues.
Key timeout-minutes Supported behavior Literal or expression returning a number greater than zero and at most 360.

Tolerated failures, including evaluation failures before a process starts, produce masked log and annotation warnings without changing the conclusion. This includes composite children and JavaScript pre hooks.

steps:
  - id: test
    run: go test ./...
    continue-on-error: true
  - if: steps.test.outcome == 'failure'
    run: ./scripts/report-failure

Commands and actions

Linux and macOS support bash, sh, pwsh, powershell, python, and custom templates such as Rscript {0}, julia --color=yes {0}, or bash -l {0}. The interpreter must already be installed and on PATH; PowerShell, R, and Julia aren't installed automatically. Named PowerShell shells use their exact executable without fallback. Unsupported shells fail during compilation when known, or before their step when resolved at runtime.

Custom templates use command [options] {0} [options], where {0} receives the script path. Single quotes, double quotes, and backslash escapes group arguments without shell expansion. Named PowerShell runs UTF-8 .ps1 scripts, sets $ErrorActionPreference = 'stop', and propagates the last native exit status. Custom PowerShell templates control their own error handling.

Windows defaults to pwsh and supports explicit powershell and MSYS2 custom shells; cmd is unsupported. Windows executable templates can use drive-absolute paths such as D:\cygwin\bin\bash.exe '{0}'. Quote paths containing spaces. A single backslash after the drive prefix selects literal executable separators; templates with an escaped first backslash retain escape rules.

msys2/setup-msys2@v2 can install msys2 {0}, including for defaults.run.shell. Its .cmd wrapper resolves from step PATH, including GITHUB_PATH, and receives an extensionless unmodified script and Windows working directory. MSYS2 owns login setup, errors, and path conversion. Windows .cmd custom wrappers must forward directly without CALL or delayed expansion. Arguments can contain spaces and %, !, &, and ^, but not double quotes, line breaks, NUL, or a trailing backslash. These argument limits don't restrict script contents.

Workflow working directories stay within the workspace. Composite actions can also select an absolute directory inside their own verified source repository; relative paths still use the workspace. Traversal and symlink escapes are rejected.

steps:
  - name: Test
    shell: bash
    working-directory: ./src
    run: go test ./...

uses accepts supported local and public actions with expression-valued with inputs. Direct workflow uses: docker://... is rejected; prebuilt images belong in locked action metadata. See action sources and runtimes and self-repository actions for source verification and access restrictions.

Background and parallel steps

The background, wait, wait-all, cancel, and parallel extensions are supported outside composite actions, with at most ten active background steps:

steps:
  - id: server
    run: ./scripts/start-server
    background: true
  - run: ./scripts/test
  - cancel: server
  - parallel:
      - run: ./scripts/lint
      - run: ./scripts/check-format

Use wait: <id> for selected work or wait-all: for all active work. Outputs, environment changes, and failures become visible at the covering wait. Remaining work joins before post-action cleanup.

Environment files

GITHUB_OUTPUT, GITHUB_ENV, GITHUB_PATH, GITHUB_STATE, and GITHUB_STEP_SUMMARY support multiline values. File commands ignore a leading UTF-8 BOM, not BOMs inside values. NODE_OPTIONS can't be set using GITHUB_ENV. The total job summary limit is 1 MiB.

Default environment

GITHUB_REF, GITHUB_REF_NAME, and GITHUB_REF_TYPE follow the context values. GITHUB_WORKFLOW is the top-level name or repository-relative path; GITHUB_WORKFLOW_REF and GITHUB_WORKFLOW_SHA retain top-level caller identity through reusable calls. GITHUB_WORKSPACE, build run variables, and runner identity follow the runtime context table. Workflow, job, step, action, and environment-file values can't override protected runtime identity variables.

Workflow commands

Command Behavior
Command add-mask, stop-commands Behavior Supported.
Command warning, error Behavior Buildkite build annotations.
Command group, endgroup Behavior Linear log sections.
Command Debug and matcher commands Behavior Accepted, no presentation effect.
Command notice, command echo control, other legacy commands Behavior Unsupported.

Expressions and contexts

Conditions, runtime fields, and compile-time fields have different context access. An expression supported in one position isn't necessarily supported in another.

Syntax Availability
Syntax !, &&, `\ Availability \
Syntax always(), success(), failure(), cancelled() Availability Conditions, without arguments.
Syntax startsWith(), contains(), endsWith(), format(), join(), toJSON(), fromJSON(), case() Availability Conditions and listed runtime/compile-time fields, subject to context and result-type restrictions.
Syntax hashFiles() Availability Workflow/composite step fields and conditions, plus JavaScript lifecycle conditions; not general compile-time fields.

Conditions

Conditions use GitHub-style truthiness, loose numeric coercion, case-insensitive string comparisons, operand-returning &&/||, primitive string conversion, array search in contains(), and lazy case() with 3โ€“255 odd-numbered arguments and Boolean predicates. Unlisted functions are unsupported. Every branch is validated before lazy evaluation, so an unreachable branch can't hide an unsupported context or function.

Missing properties in available contexts evaluate to null; unavailable contexts are errors. Computed object indexes, numeric array indexes, whole matrix and needs objects, step-scoped steps, and .* projections are supported. Missing indexes yield null, projections omit absent children, and another wildcard flattens one level. [*] is unsupported. Whole/dynamic github is unsupported except access rooted at github.event; whole inputs and strategy are unsupported.

Context Job condition Step condition
Context github.actor, base_ref, event_name, head_ref, ref, ref_name, ref_type, repository, repository_owner, sha, workflow_ref, workflow_sha Job condition Yes Step condition Yes
Context github.event, including projected/dynamic properties Job condition Yes Step condition Yes
Context github.server_url Job condition No Step condition Composite steps only, from v0.105.0.
Context runner.os, runner.arch, runner.environment Job condition Yes Step condition Yes
Context runner.temp, runner.debug Job condition No Step condition Yes; debug is always absent.
Context needs.<job>.result, needs.<job>.outputs.<name> Job condition Yes Step condition Yes
Context Matrix values Job condition Yes Step condition Yes
Context Scalar strategy properties Job condition No Step condition Workflow steps only.
Context vars Job condition Repository/organization scope Step condition Environment/repository/organization scope.
Context Named or computed input access Job condition Yes Step condition Yes
Context Step outcome, conclusion, and outputs Job condition No Step condition Yes
Context env Job condition No Step condition Yes
Context job.status, job.services IDs, networks, ports Job condition No Step condition Yes
Context secrets, other unlisted contexts Job condition No Step condition No

hashFiles() conditions accept 1โ€“255 literal or direct-reference arguments. Reusable-call conditions use the narrower caller scope. Event data remains the original event, including whole or dynamic access.

Composite github.server_url conditions can distinguish providers, such as github.server_url == 'https://github.com'. Workflow job/step, reusable-call, and JavaScript lifecycle conditions don't gain this field. A composite condition doesn't grant token authority to its run body.

Runtime interpolation

Workflow step run, env, with, name, explicit shell, working-directory, continue-on-error, and timeout-minutes accept the supported operators and pure functions. Computed indexes and projections over available matrix, inputs, env, vars, and runner values are supported. Computed/projected needs and computed/whole/projected steps access aren't. Unavailable background outputs are errors.

toJSON(needs) serializes direct dependencies only, each with result and an outputs object. Transitive dependencies aren't included. The following job fields use the same operators and pure functions with narrower contexts:

Job field Contexts
Job field continue-on-error Contexts github, needs, strategy, matrix, vars, inputs
Job field env Contexts github, needs, strategy, matrix, vars, secrets, inputs
Job field defaults.run Contexts github, needs, strategy, matrix, env, vars, inputs
Job field outputs Contexts github, needs, strategy, matrix, job, runner, env, vars, secrets, steps, inputs

Workflow env follows job-env rules except strategy. Workflow run defaults support direct context references only. Scalar strategy properties and static bracket access work in listed workflow step and job fields, not job conditions, workflow-level fields, reusable-call inputs, or action-authored metadata including composite steps. Whole, computed, and projected strategy access is unsupported.

Null event members interpolate as empty strings. Event values and substituted inputs can't create new expression regions. || selects the right operand for false, zero, empty string, or null; it doesn't recover from evaluation errors, retrieve missing secrets, or admit expressions in literal-only fields. Expression-valued step timeouts must be numeric; continue-on-error must be Boolean.

From v0.103.0, workflow/composite step fields, JavaScript lifecycle conditions, and job outputs can read job.status as success, failure, or cancelled. Outputs see final status after posts. Job/reusable-call conditions, job env, defaults, containers/services, and Docker action arguments can't read it. Action input defaults support one direct ${{ job.status }} expression. Other job fields are unsupported except the listed service context and empty job.check_run_id input defaults.

job.status doesn't replace status functions. Use always() when a step must run after failure:

steps:
  - if: always()
    env:
      STATUS: ${{ job.status }}
    run: echo "Job status is $STATUS"

Inside a composite, a child failure can make failure() true while job.status stays at the enclosing job's status until the composite finishes. Step timeout isn't whole-job cancellation; whole-job cancellation sets cancelled.

Workflow-authored github.token references are step-only. Effective action input defaults can also request it. Exact case-insensitive toJSON(github) is supported in step runtime fields and includes the retained context and token, sorted with two-space indentation. It requests token authority and fails without authorization; composite metadata can't independently grant that authority. Job fields, action defaults, bare/dynamic/projected github, and passing whole github to other functions remain unsupported, except event-rooted access.

Retained runtime context Value
Retained runtime context github.actor, event_name, ref, repository, sha Value Event identity.
Retained runtime context github.repository_owner Value Owner from repository identity.
Retained runtime context github.server_url Value Event repository provider URL.
Retained runtime context github.job, github.workflow Value Job ID and workflow name or fallback path.
Retained runtime context github.workflow_ref, github.workflow_sha Value Top-level caller workflow identity, retained through reusable calls.
Retained runtime context github.head_ref, github.base_ref Value Pull request branches; empty for other events.
Retained runtime context github.ref_name, github.ref_type Value Short ref and branch/tag kind. Pull request short refs use <number>/merge or /head. SHA-only deployments use empty ref/name with type branch.
Retained runtime context github.action_path Value Composite directory within its steps; empty elsewhere.
Retained runtime context github.action Value From v0.104.0, the invoking workflow step's authored ID or generated name in runtime step expressions. Nested composites and pre/main/post hooks retain it. Conditions and compile-time expressions don't support it.
Retained runtime context github.action_repository, github.action_ref Value Remote composite repository and requested ref; empty for local composites or outside composite steps, subject to self-reference behavior.
Retained runtime context github.workspace Value Host checkout or fixed job-container workspace.
Retained runtime context github.run_id, run_number, run_attempt Value Buildkite build ID, number, and retry count plus one; require a Buildkite build. These aren't GitHub run IDs or valid GitHub run URLs.
Retained runtime context github.token Value Authorized token only.
Retained runtime context github.event Value Original verified event payload.
Retained runtime context runner.os, runner.arch Value Linux/X64, Linux/ARM64, macOS/ARM64, or Windows/X64.
Retained runtime context runner.environment Value self-hosted, including Buildkite hosted agents.
Retained runtime context runner.temp Value Canonical RUNNER_TEMP, available after setup in step fields and job outputs.
Retained runtime context runner.debug Value Always absent; interpolation is empty and comparison to enabled value 1 is false. Available in step fields, outputs, lifecycle conditions, and action input defaults.

This isn't the full GitHub context. job.check_run_id action input defaults, including static indexing, return an empty string because there is no GitHub Actions check run.

hashFiles() is available in workflow and composite step fields and conditions, not job fields, action input defaults, or composite output metadata. It sees earlier workspace changes and may evaluate separately for JavaScript pre and main. Composite patterns use the workspace, not the action directory or working directory.

Patterns apply in order: ! excludes, later positives can reinclude, directories include descendants, hidden files match, and overlapping matches hash once. Matching is case-insensitive on Windows only. No match returns an empty string. When a lockfile is required, check for an empty hash before caching:

steps:
  - id: lockfile
    shell: sh
    env:
      HASH: ${{ hashFiles('package-lock.json') }}
    run: |
      if [ -z "$HASH" ]; then
        echo "package-lock.json missing; check checkout order and pattern" >&2
        exit 1
      fi
      printf 'digest=%s\n' "$HASH" >> "$GITHUB_OUTPUT"
  - uses: actions/cache@v5
    with:
      path: ~/.npm
      key: npm-${{ runner.os }}-${{ steps.lockfile.outputs.digest }}

Hashing uses SHA-256 file digests in lexical path order; multi-file results can differ from GitHub's unspecified traversal order. Patterns can't be absolute or contain .. or ASCII controls. Traversal stays inside the workspace and doesn't enter symlink directories; matching a symlink or special file fails deliberately, unlike GitHub's file-symlink support.

Traversal starts at each positive pattern's literal prefix. Negative patterns don't prune traversal. Entry and time budgets count inspected nonmatching entries and cover traversal, matching, sorting, hashing, and verification. Use narrower positive paths after a budget failure. Earlier deadlines and cancellation still apply; blocked filesystem calls can't be interrupted between operations. Errors identify step/field context and affected patterns.

Event file

With a linked webhook or explicit event snapshot, the complete event payload is available as a JSON file. Access its path with either the GITHUB_EVENT_PATH environment variable or the ${{ github.event_path }} expression.

steps:
  - name: Read the event file with an environment variable
    run: jq . "$GITHUB_EVENT_PATH"
  - name: Read the event file with an expression
    env:
      EVENT_FILE: ${{ github.event_path }}
    run: jq . "$EVENT_FILE"

The file contains the payload object, not the snapshot wrapper; formatting can differ. Each job has its own file outside the checkout and writable runner temp. The file remains available through action posts and is removed at teardown. Environment overrides cannot replace its path. Environment fallback events create no file; the expression returns an empty string.

github.event_path is available in runtime environment values, step expressions and conditions, action input defaults, JavaScript lifecycle conditions, and composite and job outputs. Graph-time fields such as runs-on and concurrency, and job or reusable-call conditions evaluated before setup, cannot use the expression.

Shell steps, JavaScript lifecycle hooks, composites, and Docker actions can read the file. Host jobs use the host path; job containers use /github/workflow/event.json. Containers mount the file read-only. Docker action environment paths, including INPUT_*, translate to the mount; arguments stay literal. Pass the expression through an input and read its environment variable, or read GITHUB_EVENT_PATH inside the action.

See event payload security.

Compile-time expressions

Statically resolvable fields support github.actor, base_ref, event_name, head_ref, ref, ref_name, ref_type, repository, repository_owner, sha, and workflow, plus event data and the field-specific matrix, input, strategy, and variable contexts described above. Workflow concurrency has the additional build-ID exception from v0.106.0. Computed indexes and projections work only when the complete value resolves before execution. Whole/dynamic github requires an event root. Action and workflow uses references remain static, regardless of event data.

Limits and troubleshooting

Use the result and diagnostic guidance below when a workflow validates but doesn't run as expected. Check the selected importer/runtime version and event evidence before changing workflow permissions or filters.

Results, retries, and cancellation

  • A runtime-skipped job remains successful in Buildkite Pipelines, appends (skipped) to its label, and publishes logical skipped to downstream imported jobs.
  • Retry the whole build after ambiguous producer results/artifacts or a retried producer whose deferred jobs already expanded. A deferred step itself can be retried safely.
  • Cancellation targets the process tree. Linux/macOS send SIGINT, then SIGTERM after 7.5 seconds, then SIGKILL after another 2.5 seconds. Windows terminates through a Job Object without signal grace.
  • Summary, annotation, or skipped-label publication failures warn without changing completed job results.

Compatibility diagnostics

Diagnostics include a concise message and, when available, a detail explaining the restriction or resolved action version. Text and JSON reports and job annotations retain both; provider check summaries show the message. Follow guidance for the specific diagnostic rather than treating every rejection as a permissions or installation problem.

Aggregate workflow upload

Native GitHub Actions pipelines run one server-selected workflow per build. The server supplies workflow identity and event metadata, which the runtime checks against the repository and checkout. Customers don't configure these values. A missing or untracked server-selected workflow fails preparation. See pipeline trigger selection for the read-only identity fields.

The remaining aggregate-upload behavior describes advanced custom importers and older explicit-workflow pipelines, not native setup. A custom importer can pass explicit workflow paths to the CLI upload command. Explicit selections require an importer step key, and their generated groups depend on it.

Explicit paths must be regular, tracked .yml or .yaml files inside the repository. Missing or untracked configured paths warn and are omitted; if all are omitted, upload succeeds without adding a pipeline. A missing server-selected workflow fails. Directories, tracked-but-missing files, outside paths, symlinks, and globs fail. A custom importer may use one explicit outside or untracked file, except when deferred jobs require committed source. Paths are normalized, sorted, and deduplicated.

Server-selected workflows have no workflow group. Explicit selections keep groups, including a single selection, and require an importer step key. Server-selected importers don't require a key. Group labels use :github: workflow ยท <workflow-name-or-path> and append a nonempty run-name. The importer label and build message don't change. Provider checks use <workflow-name-or-path> / <job-id> (<event>), with matrix values appended to the job ID. GitHub and Origin events publish checks to their respective providers.

A workflow that doesn't declare the event becomes one top-level skipped step. The importer annotation links to skipped workflows and explains event or filter exclusions, including when all workflows skip. Reusable-only files remain callable but don't create groups; selecting only reusable workflows is an error.

In an aggregate upload, a parse, compilation, or trigger-translation error replaces the affected workflow with a failing item, redacted diagnostics, and a Workflow could not be run check. Check summaries are limited to 65,535 bytes. Other workflows can continue. Within a fully expanded static graph, job-level compilation errors can fail only the affected jobs and skip their dependants while independent jobs run. Workflows with deferred jobs fail as a whole on compilation errors. Errors before the graph is known also fail the workflow as a whole.

Single-workflow parse errors, invalid paths, event-input errors, admission failures, artifact failures, and upload failures abort the upload transaction. Upload doesn't publish a partial transaction. A compiler error takes precedence over a same-workflow filter exclusion.

Key limits

Item Limit
Item Workflow file Limit 1 MiB
Item Expanded jobs per workflow Limit 1,024
Item Matrix instances per job Limit 256
Item Reusable nesting Limit Four levels
Item Nested local actions Limit Ten levels
Item Active background steps Limit Ten
Item Job outputs Limit 64 values, 1 MiB each, 4 MiB combined from v0.102.3
Item Output-derived matrix JSON Limit 64 KiB from v0.102.3; row strings at most 1 KiB
Item Output-derived runner selection Limit 1 KiB
Item Job or step timeout Limit 360 minutes
Item Artifacts per job Limit 64
Item Upload files Limit 10,000 per artifact
Item Download files Limit 10,000 across all selected artifacts
Item Artifact source data or ZIP bytes Limit No separate runtime limit; agent/storage limits apply.
Item Job summary Limit 1 MiB
Item hashFiles() patterns Limit 255 per call, 1 KiB each, 64 KiB combined
Item hashFiles() inspected entries Limit 1,000,000 per call
Item hashFiles() execution Limit 30 seconds per call
Item hashFiles() matches Limit 10,000 files per call
Item hashFiles() selected bytes Limit 1 GiB per call

Compatibility policy

buildkite-gha follows exposed, reproducibly observed GitHub Actions behavior within the supported subset on this page. Intentional differences and security restrictions are stated where they apply. Unsupported functionality is a Buildkite Pipelines limit, not a claim that GitHub rejects the same workflow.

This reference covers GitHub Actions Compatibility and the hosted profile used by upload. A runtime release doesn't by itself enable backend capabilities or update existing webhook subscriptions.

This public reference owns shipped support and user limits, and is updated when released behavior or prerequisites change. The contributor compatibility contract defines implementation obligations, evidence requirements, and documentation ownership.