GitHub blocks pull_request_target by default on 2 December. Find the workflows it will break this week
Two two-hour brownouts, on 9 and 16 November, will fail real runs first. A short audit script lists the workflows that use the trigger and flags the ones that run fork code.

On 2 December 2026, GitHub Actions starts refusing pull_request_target runs in public repositories unless a policy explicitly allows them. That is a change from the date GitHub gave earlier: enforcement was due on 2 November, and the changelog entry from 2 November
moves it a month. Before then come two brownouts, 9 and 16 November from 4pm to 6pm UTC, when the block is enforced for real. Affected workflows fail with an error annotation instead of running, and go back to warnings once the window closes.
This isn’t only a maintainer’s problem. Agents edit workflow files, and pull_request_target is the trigger a model reaches for when a fork’s pull request can’t see a secret. If your repository is public and has had an agent anywhere near .github/workflows, check it before the 9th.
What the block covers
Private and internal repositories aren’t affected. The change applies to public repositories on GitHub Free, Team and Enterprise Cloud, and not to Enterprise Server. A repository that already has an applicable Actions event policy keeps it: according to GitHub’s docs, the default doesn’t replace a policy you’ve configured.
The default currently runs in evaluate mode, meaning runs continue but GitHub records which ones would have been blocked. Those show up under the Policy insights page for GitHub Actions policies in the repository or organisation settings. Look there first. It’s the authoritative list, because it reflects what actually ran.
You then have two options per workflow. Keep the block, or allow the trigger in an event policy, optionally narrowed to specific workflow files. The docs also say that if a workflow doesn’t need the trigger, it should move to a safer event such as pull_request.
Why agents end up on this trigger
pull_request and pull_request_target fire on the same pull request activity, but they run in different trust contexts. For a pull request from a fork, pull_request runs the workflow from the merge commit, with a read-only GITHUB_TOKEN and no other secrets. pull_request_target runs the workflow from the base repository’s default branch, with the base repository’s token and secrets.
So when CI on a fork’s pull request fails with an empty NPM_TOKEN or a missing registry credential, the shortest edit that turns the build green is to swap the trigger and point actions/checkout at the pull request head. That is a plausible thing for an agent to propose when told “make CI pass on external PRs”, and it is the exact shape GitHub’s docs call insecure:
# INSECURE: the shape GitHub's docs warn about
on:
pull_request_target:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
ref: ${{ github.event.pull_request.head.sha }}
- run: npm ci && npm test
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
The checkout alone doesn’t execute anything. The next step does: npm ci runs install scripts from the fork’s package.json, and npm test runs whatever the fork says a test is. Anyone who can open a pull request can run code with your secrets. The docs call this a “pwn request”, and note it is the root cause of several supply-chain compromises.
That is opinion, not a measurement: nothing here claims agents write this shape often. The point is narrower. A reviewer skimming a one-line trigger change in an agent’s diff can miss what it does, and this change makes the failure visible earlier. If you want a guardrail beside it, the earlier post on catching agent pull requests that weaken CI covers a diff check for workflow edits.
Audit your own workflows
Insights only shows what has already run. A script over the repository answers a different question: which files use the trigger, and which of those check out fork code and then run it. The script below needs Python and PyYAML (pip install pyyaml). It reads every workflow, reports the ones with pull_request_target, and flags a job that uses actions/checkout with a pull request head ref, a refs/pull/ ref, or allow-unsafe-pr-checkout, followed by a run step.
#!/usr/bin/env python3
"""List workflows that use pull_request_target and flag the pwn-request shapes."""
import pathlib
import re
import sys
import yaml # pip install pyyaml
HEAD = re.compile(r"pull_request\.head\.|refs/pull/|allow-unsafe-pr-checkout")
def triggers(doc):
on = doc.get(True, doc.get("on")) # YAML 1.1 reads a bare `on` as True
if isinstance(on, str):
return {on}
if isinstance(on, list):
return set(on)
return set(on or {})
def audit(path):
doc = yaml.safe_load(path.read_text())
if "pull_request_target" not in triggers(doc):
return None
findings = []
for name, job in (doc.get("jobs") or {}).items():
steps = job.get("steps") or []
for i, step in enumerate(steps):
if str(step.get("uses", "")).startswith("actions/checkout") and HEAD.search(str(step.get("with", ""))):
runs = [s for s in steps[i + 1:] if "run" in s]
if runs:
findings.append(f"job '{name}' checks out fork code, then runs it")
else:
findings.append(f"job '{name}' checks out fork code (no run step after it)")
return findings
root = pathlib.Path(sys.argv[1] if len(sys.argv) > 1 else ".")
hits = 0
for f in sorted((root / ".github" / "workflows").glob("*.y*ml")):
result = audit(f)
if result is None:
continue
hits += 1
verdict = "REVIEW" if result else "likely fine"
print(f"{verdict:12} {f.relative_to(root)}")
for line in result:
print(f" - {line}")
print(f"{hits} workflow(s) use pull_request_target")
sys.exit(1 if hits else 0)
Run against a repository with four workflows (a labeller that only calls actions/labeler, the insecure test job above, a plain push and pull_request build, and a commenting job that checks out a fork’s branch with allow-unsafe-pr-checkout: true and runs a script), it prints:
REVIEW .github/workflows/ci.yml
- job 'test' checks out fork code, then runs it
REVIEW .github/workflows/comment.yml
- job 'c' checks out fork code, then runs it
likely fine .github/workflows/label.yml
3 workflow(s) use pull_request_target
The build workflow isn’t listed because it never used the trigger. The nonzero exit code means the script can sit in CI as a check once you’ve fixed what it finds.
Treat “likely fine” as a prompt to look, not a verdict. The script only reads the actions/checkout step, and GitHub’s docs are explicit that the checkout protection covers fork pull request refs only. Code fetched with git fetch or gh pr checkout, or an artifact downloaded from a fork’s run and then executed, gets past both the docs’ protection and this script.
What to do with each result
For the labeller, the triage job or the comment bot that never runs fork code, you’ve got a real choice. These are what the trigger was built for. If the workflow needs the token to write labels or comments, allow it in an event policy, scoped to that workflow file, and set the job’s permissions to the least it needs.
For a job that tests fork code with a secret, switch to pull_request. If the tests truly need a credential, the docs’ suggestion is to split the work: run the untrusted code in an unprivileged workflow, and do the privileged step (posting a status, say) in a separate one that treats everything from the first as data. A downloaded artifact from a fork’s run counts as untrusted input.
Two smaller facts matter for the second case. Workflows triggered by pull_request_target get read-only access to the default branch’s cache scope, so a cache save fails with a warning while the job continues. And allow-unsafe-pr-checkout is named to stand out in review, so make a rule that any diff containing it needs a human to sign off.
Before the first brownout
You’ve got until 9 November at 4pm UTC. Open the Policy insights page for your most active public repository, run the script from the repository root, and compare the two lists. A workflow in both is the one to fix first.