#!/usr/bin/env bash # Resolve a PR's exact head (spec ยง10.1) and record its metadata + CI status. # # Usage: resolve-pr-head.sh # Reads: GH_TOKEN (read-only App installation token) # Writes: $ARTIFACTS_DIR/-pr.json (metadata consumed by evidence + agent) # _sha / _short_sha to $GITHUB_OUTPUT when present. SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # shellcheck source=lib.sh source "$SCRIPT_DIR/lib.sh" side="${1:?side required}" repo="${2:?repo required}" pr="${3:?pr number required}" require_env GH_TOKEN pr_json="$(gh api "repos/$repo/pulls/$pr")" || die "failed to fetch $repo PR #$pr" state="$(jq -r '.state' <<<"$pr_json")" head_sha="$(jq -r '.head.sha // empty' <<<"$pr_json")" head_repo="$(jq -r '.head.repo.full_name // empty' <<<"$pr_json")" [ -n "$head_sha" ] || die "$repo PR #$pr has an empty head" [ "$state" = "open" ] || log "WARNING: $repo PR #$pr state is '$state', not open" [ "$head_repo" = "$repo" ] || die "$repo PR #$pr head lives in '$head_repo' (fork heads are not supported)" short_sha="${head_sha:0:7}" # CI status on the exact head: green / red / pending / missing / unknown. # # A failed API call must NEVER be reported as "missing": that would state as a # fact ("this PR has no CI") what is actually an unread signal, which is the # exact failure mode this runner exists to prevent. Reading check-runs needs the # App's `checks: read` permission; without it the call 403s and the honest # answer is "unknown". checks_err="$ARTIFACTS_DIR/$side-checks-error.txt" if checks_json="$(gh api "repos/$repo/commits/$head_sha/check-runs" \ --jq '{total: .total_count, runs: [.check_runs[] | {name, status, conclusion}]}' 2>"$checks_err")"; then ci_status="$(jq -r ' if .total == 0 then "missing" elif ([.runs[] | select(.status != "completed")] | length) > 0 then "pending" elif ([.runs[] | select(.conclusion != "success" and .conclusion != "neutral" and .conclusion != "skipped")] | length) > 0 then "red" else "green" end' <<<"$checks_json")" rm -f "$checks_err" else reason="$(tr -d '\000-\037' <"$checks_err" | cut -c1-160)" log "WARNING: could not read check-runs for $repo@$short_sha: $reason" ci_status="unknown (check-runs unreadable; the review App likely lacks 'checks: read')" checks_json='{"total":0,"runs":[],"unreadable":true}' fi jq -n \ --arg side "$side" --arg repo "$repo" --argjson pr "$pr" \ --arg title "$(jq -r '.title' <<<"$pr_json")" \ --arg head_ref "$(jq -r '.head.ref' <<<"$pr_json")" \ --arg base_ref "$(jq -r '.base.ref' <<<"$pr_json")" \ --arg head_sha "$head_sha" --arg short_sha "$short_sha" \ --arg state "$state" --arg ci_status "$ci_status" \ --argjson mergeable "$(jq '.mergeable' <<<"$pr_json")" \ --argjson checks "$checks_json" \ '{side: $side, repo: $repo, pr: $pr, title: $title, head_ref: $head_ref, base_ref: $base_ref, head_sha: $head_sha, short_sha: $short_sha, state: $state, mergeable: $mergeable, ci_status: $ci_status, checks: $checks}' \ >"$ARTIFACTS_DIR/$side-pr.json" out="${GITHUB_OUTPUT:-/dev/stdout}" { printf '%s_sha=%s\n' "$side" "$head_sha" printf '%s_short_sha=%s\n' "$side" "$short_sha" } >>"$out" log "$side: $repo#$pr head=$short_sha base=$(jq -r '.base.ref' <<<"$pr_json") ci=$ci_status"