On December 8, 2025, GitHub will change how pull_request_target resolves workflow source and how environment branch protections are evaluated for pull‑request events. If you run CI/CD on Actions, you have a few weeks to adjust before approvals stop matching and jobs fail in confusing ways. Here’s the plain‑English version, what breaks, and a fast migration plan you can run today. (github.blog)
What exactly is changing in pull_request_target?
Two core shifts land on December 8:
First, pull_request_target always sources from the default branch. The workflow file and checkout commit used during pull_request_target will be taken from your repo’s default branch—no exceptions. Previously, GitHub could execute outdated workflow files from a non‑default base branch. After Dec 8, the ref and SHA align to the default branch so remediation happens in one place. (github.blog)
Second, environment branch protection rules evaluate against the executing ref, not the PR head. For the pull_request family, evaluation happens against the merge ref (for example, refs/pull/123/merge). For pull_request_target, evaluation happens against the default branch. If your environment filters were targeting feature branches (like release/*), they may no longer match. (github.blog)
Who’s at risk—and what breaks on Dec 8?
If you depend on pull_request_target for cross‑repo or fork PRs and you also rely on environment branch filtering to guard secrets, expect surprises. Typical symptoms: jobs suddenly show “no matching environments,” approvals disappear, or deployments skip with policy errors. Teams that pinned protections to release/* or similar will notice non‑matches because refs/pull/<id>/merge and the default branch don’t fit those patterns. (github.blog)
Security‑wise, this is the right direction: it closes known classes of issues where outdated workflows executed under pull_request_target. But it means you must verify every place you were counting on environment filters to arbitrate secrets for PR jobs. (github.blog)
30‑minute triage checklist
Block off half an hour. You’ll surface 80% of the issues before lunch.
- Find all workflows using
pull_request_target. Search your org foron: [pull_request_target]andon:blocks with that event. Note every job that touches secrets, environments, deploys, or approvals. Pair this with a review of your reusable workflow callers. (github.blog) - Map environment filters to the new evaluation refs. For
pull_request, plan onrefs/pull/*/merge. Forpull_request_target, plan on your default branch name (oftenmain). Update environment rules or split environments if needed. (github.blog) - Check least privilege. Set
permissions: contents: readby default and grant minimal write scopes per job. If you don’t need secrets, preferpull_requestoverpull_request_target. (github.blog) - Run a safe rehearsal. Open a draft PR from a fork (or a throwaway fork) and watch which environments attach. Confirm that approvals appear where expected and that no deployment leaks happen.
- Document who approves what. If environments move or split, ensure your approvers list follows the new targets rather than the old branch patterns.
Before/after patterns that survive Dec 8
Here are practical swaps that keep your PR flows safe without slowing developers to a crawl.
Replace risky PR automation with a workflow_run gate
For builds that only need compile/test on PRs, use pull_request. For anything that needs secrets or deploy rights, trigger a workflow_run after merge to default. It’s boring—and robust.
# Before: PR deploy using pull_request_target
name: pr-deploy
on: [pull_request_target]
jobs:
deploy:
permissions: write-all
environment: preview
steps:
- uses: actions/checkout@v4
- run: ./scripts/deploy-preview.sh
# After: build on PR, deploy on merge to default
name: pr-build
on: [pull_request]
jobs:
build:
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- run: npm ci && npm test
---
name: deploy-on-merge
on:
workflow_run:
workflows: ["pr-build"]
types: ["completed"]
jobs:
deploy:
if: ${{ github.event.workflow_run.conclusion == 'success' && github.ref == 'refs/heads/main' }}
environment: preview
permissions:
contents: read
deployments: write
steps:
- uses: actions/checkout@v4
- run: ./scripts/deploy-preview.sh
This pattern avoids running deployments in PR context entirely, eliminating the thorny edge cases pull_request_target was meant to solve. (github.blog)
If you must keep pull_request_target, make it tight
Some checks—like applying repository labels or commenting with ephemeral data—may justify pull_request_target. When you can’t avoid it:
- Use
permissionswith least‑privilege scopes; keep the default token read‑only unless a specific step needs write. (github.blog) - Don’t execute user‑controlled code. Avoid
runsteps that parse PR content or shell out with untrusted input. - Move deploy logic to a separate, post‑merge workflow or a dispatch that requires an approver.
- Update environment branch filters to match the default branch for
pull_request_target. (github.blog)
Adjust your environment patterns
Many orgs used environment branch rules like release/* or feature/*. After Dec 8, those won’t match PR events. Use patterns that explicitly include refs/pull/*/merge for pull_request, and your default branch for pull_request_target. Example:
# environment "preview"
allowed-branches:
- refs/pull/*/merge # PR builds
# environment "production"
allowed-branches:
- refs/heads/main # pull_request_target evaluation happens here
This mirrors the new evaluation semantics and prevents confusion when approvers don’t see prompts. (github.blog)
People also ask
Do I have to stop using pull_request_target?
No—but you should default to pull_request unless you have a real need for elevated permissions with forked PRs. If you keep pull_request_target, treat it like production code with strict permissions and no untrusted inputs. The Dec 8 change makes it safer by sourcing from the default branch, but not risk‑free. (github.blog)
Will my approvals disappear?
They might, if your environment branch filters don’t match the new refs. Add refs/pull/*/merge for pull_request jobs and the default branch for pull_request_target jobs. Test with a draft PR from a fork to be sure. (github.blog)
How does this interact with other November/December deadlines?
Two notable dates collide this season. First, the macOS 13 runner image retires on December 4, 2025, with brownouts already staged across November; migrate to macos-latest or specific 14/15 labels, preferably arm64 where possible. Second, image deprecations removed Node.js 18 and Ruby 3.1 from hosted runners starting early November on a rolling schedule—pin your required versions or upgrade. (github.blog)
Key dates, versions, and gotchas
- Dec 8, 2025:
pull_request_targetand environment branch evaluation changes take effect. Validate environments and approvals before this date. (github.blog) - Dec 4, 2025: macOS 13 runner image retirement; multiple November brownouts were scheduled to nudge migration. If you still target
macos-13, fix that now. (github.blog) - From Nov 3, 2025: runner image cleanups removed legacy toolchains (e.g., Node 18, Ruby 3.1, Android NDK 26; GCC 9/10 on Ubuntu 22.04). Expect failures if you implicitly relied on them. (github.com)
Gotchas I’ve seen this week: environment names reused across repos with different rules; reusable workflows that silently inherit stricter permissions; and branch filters defined in UI while developers edit YAML—causing mismatched expectations between code and policy. Do a side‑by‑side check.
Let’s get practical: a safe ref and checkout strategy
With the new semantics, I recommend checking out a known‑good ref explicitly in PR builds, and keeping deploy steps in post‑merge workflows. Example:
jobs:
build:
permissions:
contents: read
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
fetch-depth: 1
- run: npm ci && npm test -- --reporter=junit
This reads the PR head SHA for building and testing without granting deploy rights during the PR. Combine with a separate workflow_run deployment after a protected merge to default.
Policy hygiene for bigger organizations
If you manage dozens of repos, centralize the change. Use the Actions settings APIs to audit and update who can run forked PR workflows, artifact retention, and self‑hosted runner policies. This keeps policy in sync instead of repo‑by‑repo spelunking. (github.blog)
Also consider aligning CI/CD ownership with custom repository roles for Actions—separate who manages runners, environments, and secrets from general maintainers so a single misconfiguration can’t derail shipping. (github.blog)
What to do next (developers)
- Search every repo for
pull_request_targetand annotate where secrets/environments are used. - Switch deploys to
workflow_runon protected merges; keep PR builds onpull_request. - Update environment branch rules: add
refs/pull/*/mergeand ensure the default branch is included where needed. (github.blog) - Run a forked PR dress rehearsal and confirm approvals appear and secrets stay sealed.
- Pin runner images and tool versions explicitly; don’t assume Node, Ruby, or NDK versions are present. (github.com)
What to do next (engineering managers & product owners)
- Assign a named owner for Actions policy and environments across your org.
- Schedule a 60‑minute “Dec 8 readiness” session this week with a live PR rehearsal.
- Track macOS runner migration separately. If you still see
macos-13in your fleet, block time to move to 14/15 labels before Dec 4. (github.blog) - Budget one sprint hour for policy checks using the Actions settings APIs to keep repos aligned. (github.blog)
Related deep dives and hands‑on help
If you want a prescriptive walk‑through of the ref and environment changes, our piece on pull_request_target fixes by Dec 8 includes diffs you can paste into real pipelines. If you’re also racing the macOS deadline, see the macOS 13 deprecation guide. For a broader November roundup, use what to fix this week in GitHub Actions, then bring us in via services if you want us to audit and harden your org’s CI in one pass.
Risk, limitations, and edge cases
Edge case one: monorepos with multiple release branches. Because pull_request_target always resolves to the default branch, branch‑specific workflows won’t execute under PR context anymore. Move those checks to post‑merge jobs or gate them behind dispatches approved by maintainers. (github.blog)
Edge case two: environments reused across repos. A shared “preview” environment with divergent branch rules causes inconsistent approvals after the change. Fork the environment or consolidate the policies centrally so the evaluation ref is predictable everywhere. (github.blog)
Edge case three: implicit toolchain assumptions. If your setup-node step didn’t pin a version, runner image updates may put you on a newer major overnight—Node 18 is gone from November images and Ruby 3.1 was dropped. Always pin. (github.com)
A quick migration diff you can copy
This trims risk while keeping contributor ergonomics intact.
# 1) Build and test on PRs without secrets
name: pr-checks
on: [pull_request]
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci && npm run lint && npm test
# 2) Deploy after protected merge to default
name: deploy
on:
workflow_run:
workflows: ["pr-checks"]
types: [completed]
jobs:
deploy:
if: ${{ github.event.workflow_run.conclusion == 'success' && github.ref == 'refs/heads/main' }}
environment: production
permissions:
contents: read
deployments: write
id-token: write
steps:
- uses: actions/checkout@v4
- run: ./infra/deploy.sh
Result: your PRs stay fast and useful; your secrets and deploys stay on trusted refs with approvals.
Zooming out
Between the Dec 8 semantics change and the Dec 4 macOS 13 retirement, GitHub is converging on a simpler security model: trusted refs, pinned tools, and explicit approvals. That’s good news for teams who’ve been juggling exceptions and duct‑tape workarounds. But you need to do the boring work now so you can ignore it later. If you’re short on time, start with the 30‑minute checklist, ship the obvious fixes, and schedule a deeper policy pass next week. (github.blog)