Buildkite
Buildkite's dynamic pipelines accept generated JSON steps. A small adapter can select Once triggers and upload worker jobs without a repository pipeline YAML file.
Buildkite schedules workers and reports their status. Once owns the build graph inside each worker. Generate one job per matching trigger, not one job per Once action.
This example connects Buildkite to the experimental Once worker interface. There is no installed Once Buildkite plugin. It covers branch-push verification on one Linux x86-64 worker pool, not pull requests, tags, releases, or deployments.
Prepare The Service And Workers#
Configure the pipeline to receive branch pushes only. Install the generator
below as once-buildkite-pipeline in your trusted bootstrap image, outside
the candidate checkout. Keep pipeline settings, hooks, the generator, and
worker setup outside candidate control.
Set ONCE_DEFINITION_REVISION in trusted service settings to the full lowercase
commit ID containing the approved triggers. Fetch that commit in every checkout.
Do not replace this value automatically with the commit being tested. Approve
trigger changes separately, including trigger retirement.
The bootstrap needs Once, Git, and Python 3. The once-linux queue needs Once,
Git, jq, a POSIX shell, the selected toolchains, and a clean checkout of the
build's full commit ID. Ignore .once/ and keep temporary event files outside
the checkout. Use separate checkouts for concurrent jobs.
Provide an isolated execution boundary before checkout or tool setup. Do
not expose agent tokens, pipeline-upload credentials, secrets, or other jobs'
files to project code. Agent targeting and env entries are not isolation;
ordinary agent defaults do not establish this boundary. The example assumes
you already enforce it with your worker infrastructure and trusted hooks.
Generate Worker Jobs From Approved Triggers#
The adapter reads activated declarations without evaluating project modules. It matches the source ref and carries the approved definition revision and candidate commit into each generated job. It rejects unsupported build types, incompatible configurations, and an empty selection rather than silently reporting a successful verification.
#!/usr/bin/env python3
import json
import os
import subprocess
if (os.environ.get("BUILDKITE_SOURCE") != "webhook"
or os.environ.get("BUILDKITE_PULL_REQUEST") != "false"
or os.environ.get("BUILDKITE_TAG")):
raise SystemExit("This adapter accepts branch-push builds only")
approved = os.environ["ONCE_DEFINITION_REVISION"]
subprocess.run(
["once", "edit", "activate-triggers", "--revision", approved],
check=True, stdout=subprocess.DEVNULL,
)
registered = json.loads(subprocess.check_output(
["once", "--format", "json", "query", "triggers"], text=True,
))["activation"]
ref = "refs/heads/" + os.environ["BUILDKITE_BRANCH"]
revision = os.environ["BUILDKITE_COMMIT"]
steps = []
for trigger in registered["triggers"]:
if ref not in trigger["on"]["refs"]:
continue
if any((i["configuration"]["os"], i["configuration"]["arch"])
!= ("linux", "x86_64") for i in trigger["invoke"]):
raise SystemExit("This queue supports Linux x86-64 configurations only")
steps.append({
"key": "once-" + trigger["id"],
"label": "Once: " + trigger["id"],
"agents": {"queue": "once-linux"},
"timeout_in_minutes": 30,
"env": {
"ONCE_DEFINITION_REVISION": registered["revision"],
"ONCE_REF": ref,
"ONCE_REVISION": revision,
"ONCE_TRIGGER": trigger["id"],
},
"command": [
"set -eu",
'once edit activate-triggers --revision "$ONCE_DEFINITION_REVISION"',
'observation=$(mktemp)',
"trap 'rm -f \"$observation\"' EXIT",
'jq -n --arg ref "$ONCE_REF" --arg revision "$ONCE_REVISION" '
"'{event: \"vcs.ref_changed\", ref: $ref, revision: $revision}' "
'> "$observation"',
'once --memory-limit 2GiB run --trigger "$ONCE_TRIGGER" '
'--event "$observation"',
],
})
if not steps:
raise SystemExit("No approved Once trigger matches this ref")
print(json.dumps({"steps": steps}))Set your Buildkite pipeline's bootstrap command in its service settings to:
set -eu
pipeline=$(mktemp)
trap 'rm -f "$pipeline"' EXIT
once-buildkite-pipeline > "$pipeline"
buildkite-agent pipeline upload --no-interpolation "$pipeline"Writing the output before uploading ensures generator failure stops the
bootstrap instead of uploading partial work. --no-interpolation leaves
worker variables and jq expressions untouched until the worker runs. Stable
step keys make repeated uploads fail instead of adding duplicate jobs. Do
not automatically retry a bootstrap that may already have uploaded steps.
The approved trigger supplies the target selections and configurations. The adapter supplies only service concerns: worker routing, timeout, and the per-job memory budget. Adapt routing for additional worker pools; declaring an OS in Once does not provision or isolate a machine with that OS.
Validate Before Connecting A Real Pipeline#
With a representative clean checkout and Buildkite environment, inspect the generated pipeline without changing a running build:
once-buildkite-pipeline > /tmp/once-pipeline.json
buildkite-agent pipeline upload --no-interpolation --dry-run --format json \
/tmp/once-pipeline.jsonDry-run validates the pipeline format, not agent placement, source trust, required-check policy, or delivery to a real build. Test those in a disposable Buildkite pipeline before adopting the adapter.
Once requires the worker's clean HEAD to match ONCE_REVISION. A missing
capability, failed invocation, dirty checkout, or mismatched revision fails
the job. Publish the aggregate build result through Buildkite; do not turn a
missing job or a crashed worker into success. Retain run records from .once/
through trusted infrastructure if you need them after the worker exits.
Current Limits#
Buildkite still owns event authentication, worker isolation, cancellation,
required checks, and recovery. The normalized observation is not an
authenticated event receipt. These runs use a dedicated local cache and do
not provide immutable replay. --memory-limit budgets work within one Once
process; enforce OS resource limits and limits across jobs separately.
See Event-Driven Automation for the trigger contract and integration status of other services.