Skip to content

Instantly share code, notes, and snippets.

@lox
Created June 8, 2026 20:45
Show Gist options
  • Select an option

  • Save lox/eb78d49e4debd2f5a4f2c89e8d501469 to your computer and use it in GitHub Desktop.

Select an option

Save lox/eb78d49e4debd2f5a4f2c89e8d501469 to your computer and use it in GitHub Desktop.
Buildkite pre-command hook for validating and repairing stale GitHub PR merge-ref checkouts
#!/usr/bin/env bash
set -euo pipefail
# Only applies to GitHub PR merge-ref builds.
[[ "${BUILDKITE_PULL_REQUEST:-false}" != "false" ]] || exit 0
[[ "${BUILDKITE_PULL_REQUEST_USING_MERGE_REFSPEC:-false}" == "true" ]] || exit 0
pr="$BUILDKITE_PULL_REQUEST"
remote="${BUILDKITE_GIT_REMOTE:-origin}"
retry_later() {
echo "PR merge ref is not current yet: $*" >&2
exit 128
}
fail_closed() {
echo "Cannot safely validate PR merge ref: $*" >&2
exit 1
}
expected_head="${EXPECTED_PR_HEAD_SHA:-}"
expected_base="${EXPECTED_PR_BASE_SHA:-}"
# Best current source for PR webhook builds. Prefer explicit env if available.
if [[ -z "$expected_head" || -z "$expected_base" ]]; then
webhook="$(buildkite-agent meta-data get buildkite:webhook 2>/dev/null || true)"
if [[ -n "$webhook" ]]; then
expected_head="${expected_head:-$(jq -r '.pull_request.head.sha // empty' <<<"$webhook")}"
expected_base="${expected_base:-$(jq -r '.pull_request.base.sha // empty' <<<"$webhook")}"
fi
fi
[[ -n "$expected_head" ]] || fail_closed "missing expected PR head SHA"
[[ -n "$expected_base" ]] || fail_closed "missing expected PR base SHA"
tmp="refs/buildkite-pr-merge-check/${BUILDKITE_JOB_ID:-$$}"
trap 'git update-ref -d "$tmp/head" >/dev/null 2>&1 || true; git update-ref -d "$tmp/merge" >/dev/null 2>&1 || true' EXIT
git fetch --no-tags --force "$remote" \
"+refs/pull/$pr/head:$tmp/head" \
"+refs/pull/$pr/merge:$tmp/merge"
live_head="$(git rev-parse "$tmp/head^{commit}")"
live_merge="$(git rev-parse "$tmp/merge^{commit}")"
if [[ "$live_head" != "$expected_head" ]]; then
fail_closed "PR head moved from $expected_head to $live_head; start a fresh build"
fi
parents="$(git show -s --format=%P "$live_merge")"
has_parent() {
local wanted="$1"
for parent in $parents; do
[[ "$parent" == "$wanted" ]] && return 0
done
return 1
}
has_parent "$expected_head" || retry_later "merge $live_merge does not include expected head $expected_head"
has_parent "$expected_base" || retry_later "merge $live_merge does not include expected base $expected_base"
current="$(git rev-parse HEAD^{commit}")"
if [[ "$current" != "$live_merge" ]]; then
echo "Repairing stale Buildkite checkout: $current -> $live_merge" >&2
git checkout -f "$live_merge"
# Makes subsequent commands in this job see the repaired commit.
export BUILDKITE_COMMIT="$live_merge"
export BUILDKITE_COMMIT_RESOLVED="true"
# Updates Buildkite's stored PR merge commit for later jobs in the same build.
buildkite-agent meta-data set buildkite:git:commit:sha "$live_merge"
fi
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment