GitHub's 2026-03-10 REST API version ships a quiet but consequential breaking change: the merge_commit_sha property is gone from pull request responses. 21 endpoints affected. There is no error, no 410, no migration warning header — the field just stops appearing in the JSON.
For CI/CD code that does this:
const { data: pr } = await octokit.pulls.get({ owner, repo, pull_number });
await tagDeploymentArtifact(pr.merge_commit_sha);
You now tag with undefined. The deployment still ships. The artifact registry still accepts the upload. Six weeks later, somebody asks "which commit produced this build?" and the answer is undefined-1747291204.
This is incident #7 in our silent-breakage series (GitHub PushEvent, Stripe Basil, Shopify 2025-01, OpenAI Responses, Twilio regional, HubSpot Contacts v1). The pattern keeps repeating because removing a field from a JSON response is the cheapest possible breaking change for the API provider and the most invisible possible breaking change for the consumer.
What's removed and where
merge_commit_sha disappears from 21 PR-bearing endpoints, including:
GET /repos/{owner}/{repo}/pullsGET /repos/{owner}/{repo}/pulls/{pull_number}POST /repos/{owner}/{repo}/pullsPATCH /repos/{owner}/{repo}/pulls/{pull_number}GET /repos/{owner}/{repo}/issues/eventsGET /repos/{owner}/{repo}/issues/{issue_number}/events- Search results that embed PR objects
- Project card payloads that link to PRs
Same release also removes the singular assignee field from 31 Issue and PR endpoints. From the changelog:
The singular
assigneefield has been marked as 'closing down' for years and duplicates information available in theassigneesarray.
True — and the migration is mechanical. Read from assignees[0] instead of assignee. Write assignees: [login] instead of assignee: login. The footgun is that assignee returning undefined looks identical to "this PR has no assignee," and a lot of routing logic gates on exactly that.
Why you don't get an error
The REST API breaking-change rollout works by version pinning. If you send:
X-GitHub-Api-Version: 2022-11-28
…you keep the old field. If you send:
X-GitHub-Api-Version: 2026-03-10
…or no version header at all and your client SDK has updated its default — the field is gone.
Octokit, PyGithub, go-github, and the various community SDKs upgrade their defaults on their own schedules. Your package.json says ^21.0.0, the maintainer ships 21.4.7 next month with the new default version header, your npm install picks it up on the next CI run, and now your release bot is tagging undefined. Nothing in your repo changed. Nothing in the API changed for clients still pinning the old version. The change rides in on a transitive bump.
The non-obvious replacement
The official guidance points to the pull_request.merge_commit_sha field on the webhook payload (not the API response) and to the merge commit's SHA reachable via:
GET /repos/{owner}/{repo}/commits/{ref}
# where ref is the head of the default branch immediately after merge
Neither is a one-line swap.
Webhook payloads still carry merge_commit_sha because GitHub versions webhooks separately from the REST API. If your release bot is webhook-driven, you're fine — the field is in the pull_request.closed event payload. If your release bot is poll-driven (CI runs nightly, asks "what merged today, tag those artifacts"), you have to reconstruct the merge commit by:
- Reading the PR's
base.ref(the target branch) - Pulling commit history on that branch
- Finding the merge commit by the PR number in the commit message (
Merge pull request #N) - Or — for squash/rebase merges — there is no single merge SHA, and you have to use
head.shaof the PR plus tracking metadata your bot wrote earlier
The squash-merge case is the one most release tooling gets wrong on the rewrite. There never was a merge_commit_sha for squash merges in the strict sense — GitHub returned the SHA of the squashed-in commit on the base branch — but consumers treated it as canonical. Without that field, the PR head SHA and the resulting commit on main are different SHAs with no GitHub-provided link between them.
Why your tests didn't catch it
Your CI pipeline tests hit a fixture PR JSON. The fixture has merge_commit_sha. The test asserts the tag string is well-formed.
Three things have to be true for that test to fail before the rollout:
- Your fixtures were regenerated against the new API version (they weren't)
- Your test runner sends the new
X-GitHub-Api-Versionheader (it doesn't, unless you wrote it) - Your assertion checks
typeof sha === 'string' && sha.length === 40and not justsha != null(most don't)
So the test stays green. The same pattern as every other silent drift incident:
| # | API | What changed | Where tests missed it |
|---|---|---|---|
| 1 | GitHub PushEvent |
commits field silently dropped |
Tests didn't assert field presence |
| 2 | Stripe Basil |
current_period_end moved to items
|
Tests used Checkout fixtures |
| 3 | Shopify 2025-01 |
fulfillmentHold type change |
Tests mocked the response |
| 4 | OpenAI Responses |
input_text removed for assistants |
Tests covered request role=user |
| 5 | Twilio regional | Regional domains stop resolving | Tests don't hit prod DNS paths |
| 6 | HubSpot Contacts v1 |
list-memberships returns empty |
Tests asserted against sandbox fixtures |
| 7 | GitHub merge_commit_sha | Field removed from PR responses | Tests used pre-rollout fixtures |
Pattern holds. The breaking change is always in a field a test isn't asserting against — or in a layer (transitive SDK upgrade, version-pinned header) the test isn't exercising.
What to do this week
Three actions, in priority order:
Grep your codebase for
merge_commit_sha. Every read site is a candidate failure. Tag every assignment to a deployment artifact, release name, or git tag as a critical path.Grep for
pr.assigneeorissue.assignee(singular). Routing/notification code keyed on this field is the next-largest blast radius after release tagging.Pin
X-GitHub-Api-Version: 2022-11-28as a stopgap if you can't fix all the read sites this week. GitHub supports old versions for ~24 months. This buys time, not a fix — schedule the migration before the version sunsets.
If your release bot is silently tagging undefined, you usually find out 90 days later when somebody is paged at 3 AM and can't trace the deploy. Worth fixing on a Tuesday afternoon.
We built FlareCanary for this layer: poll third-party APIs on a schedule, watch the response shape, page when a field stops appearing. The GitHub merge SHA removal is the textbook incident — no error, no warning, just a field that used to be there and isn't. APIs break this way constantly. We catch it before the 3 AM page.
We track API drift incidents in real time. If your release tooling reads merge_commit_sha and you haven't audited it for the 2026-03-10 API version, that null is already in your build pipeline somewhere.
Top comments (0)