From 74f141d9d6244eb2f26706ae51691525421a1bb5 Mon Sep 17 00:00:00 2001 From: Reuven Harrison Date: Fri, 22 May 2026 00:27:44 +0300 Subject: [PATCH] actions: pin /review URLs to the immutable base commit SHA MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The free /review URL emitted by every entrypoint used $GITHUB_BASE_REF (the branch name, e.g. "main") in the base_sha query parameter. That's mutable: once the base branch advances past the commit the action ran against — for example, when a downstream PR renames or moves the spec file on main — every previously-emitted /review URL silently breaks. raw.githubusercontent.com resolves the branch to whatever the current HEAD is, not what HEAD was when CI ran. The rev_sha parameter was always pinned to the immutable head commit, but base_sha wasn't, leaving the base side of every URL exposed to drift. Switch all three URL-emitting entrypoints (breaking, changelog, pr-comment) to a three-tier fallback for base_sha: 1. pull_request.base.sha from $GITHUB_EVENT_PATH (the canonical value for pull_request triggers) 2. git rev-parse origin/$GITHUB_BASE_REF (works on push triggers where the base branch was fetched into the workspace) 3. $GITHUB_BASE_REF as the ultimate fallback (today's behavior, so this is a strict superset of the current contract) The pr-comment entrypoint already used pattern #1 with #3 as fallback; this commit adds #2 to its chain so push triggers also get an immutable SHA whenever possible. breaking and changelog gain the whole chain. Co-Authored-By: Claude Opus 4.7 (1M context) --- breaking/entrypoint.sh | 10 +++++++++- changelog/entrypoint.sh | 8 +++++++- pr-comment/entrypoint.sh | 12 ++++++++++-- 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/breaking/entrypoint.sh b/breaking/entrypoint.sh index 1d5bba3..cf77f4a 100755 --- a/breaking/entrypoint.sh +++ b/breaking/entrypoint.sh @@ -115,7 +115,15 @@ if [ -n "$breaking_changes" ] && ! echo "$breaking_changes" | head -n 1 | grep - repo="${GITHUB_REPOSITORY#*/}" head_sha=$(jq -r '.pull_request.head.sha // empty' "$GITHUB_EVENT_PATH" 2>/dev/null || echo "") if [ -z "$head_sha" ]; then head_sha="$GITHUB_SHA"; fi - free_review_url="https://www.oasdiff.com/review?owner=${owner}&repo=${repo}&base_sha=$(urlencode "$GITHUB_BASE_REF")&rev_sha=${head_sha}&base_file=$(urlencode "$base_path")&rev_file=$(urlencode "$rev_path")" + # base_sha must be an immutable commit SHA, not the branch name. Using + # $GITHUB_BASE_REF (the branch) makes the URL decay whenever the branch + # advances past the file's commit — e.g. someone merges a rename of the + # spec file and every previously-emitted /review URL starts 404'ing + # because raw.githubusercontent.com now resolves the branch to a newer + # commit where the file lives at a different path. + base_sha=$(jq -r '.pull_request.base.sha // empty' "$GITHUB_EVENT_PATH" 2>/dev/null || echo "") + if [ -z "$base_sha" ]; then base_sha=$(git rev-parse "origin/$GITHUB_BASE_REF" 2>/dev/null || echo "$GITHUB_BASE_REF"); fi + free_review_url="https://www.oasdiff.com/review?owner=${owner}&repo=${repo}&base_sha=$(urlencode "$base_sha")&rev_sha=${head_sha}&base_file=$(urlencode "$base_path")&rev_file=$(urlencode "$rev_path")" echo "::notice::📋 Review & approve these breaking changes → ${free_review_url}" echo "### 📋 [Review & approve these breaking changes](${free_review_url})" >> "$GITHUB_STEP_SUMMARY" else diff --git a/changelog/entrypoint.sh b/changelog/entrypoint.sh index ea687dc..1b4db77 100755 --- a/changelog/entrypoint.sh +++ b/changelog/entrypoint.sh @@ -107,7 +107,13 @@ if [ -n "$output" ] && ! echo "$output" | head -n 1 | grep -q "^No "; then repo="${GITHUB_REPOSITORY#*/}" head_sha=$(jq -r '.pull_request.head.sha // empty' "$GITHUB_EVENT_PATH" 2>/dev/null || echo "") if [ -z "$head_sha" ]; then head_sha="$GITHUB_SHA"; fi - free_review_url="https://www.oasdiff.com/review?owner=${owner}&repo=${repo}&base_sha=$(urlencode "$GITHUB_BASE_REF")&rev_sha=${head_sha}&base_file=$(urlencode "$base_path")&rev_file=$(urlencode "$rev_path")" + # base_sha must be an immutable commit SHA, not the branch name. Using + # $GITHUB_BASE_REF (the branch) makes the URL decay whenever the branch + # advances past the file's commit. See breaking/entrypoint.sh for the + # full rationale. + base_sha=$(jq -r '.pull_request.base.sha // empty' "$GITHUB_EVENT_PATH" 2>/dev/null || echo "") + if [ -z "$base_sha" ]; then base_sha=$(git rev-parse "origin/$GITHUB_BASE_REF" 2>/dev/null || echo "$GITHUB_BASE_REF"); fi + free_review_url="https://www.oasdiff.com/review?owner=${owner}&repo=${repo}&base_sha=$(urlencode "$base_sha")&rev_sha=${head_sha}&base_file=$(urlencode "$base_path")&rev_file=$(urlencode "$rev_path")" echo "::notice::📋 Review & approve these API changes → ${free_review_url}" echo "### 📋 [Review & approve these API changes](${free_review_url})" >> "$GITHUB_STEP_SUMMARY" else diff --git a/pr-comment/entrypoint.sh b/pr-comment/entrypoint.sh index a86fbdd..bc517d8 100755 --- a/pr-comment/entrypoint.sh +++ b/pr-comment/entrypoint.sh @@ -81,8 +81,16 @@ urlencode() { printf '%s' "$1" | jq -sRr @uri; } base_path=$(echo "$base" | sed 's/.*://') rev_path=$(echo "$revision" | sed 's/.*://') # Prefer the base SHA over the branch name so the link is commit-pinned. -free_base_sha="${base_sha:-$GITHUB_BASE_REF}" -free_review_url="https://www.oasdiff.com/review?owner=${owner}&repo=${repo}&base_sha=${free_base_sha}&rev_sha=${head_sha}&base_file=$(urlencode "$base_path")&rev_file=$(urlencode "$rev_path")" +# Fall back through `git rev-parse origin/` before resorting to +# the branch name itself, so push-event triggers (no pull_request payload) +# also get an immutable SHA in the URL whenever the base branch was +# fetched into the workspace. +if [ -n "$base_sha" ]; then + free_base_sha="$base_sha" +else + free_base_sha=$(git rev-parse "origin/$GITHUB_BASE_REF" 2>/dev/null || echo "$GITHUB_BASE_REF") +fi +free_review_url="https://www.oasdiff.com/review?owner=${owner}&repo=${repo}&base_sha=$(urlencode "$free_base_sha")&rev_sha=${head_sha}&base_file=$(urlencode "$base_path")&rev_file=$(urlencode "$rev_path")" echo "::notice::📋 View API changes → ${free_review_url}" # Build the JSON payload