Created
June 8, 2026 20:45
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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