How to test AI generated pull requests in CI
To test AI generated pull requests in CI you first have to know which of three shapes the pull request arrived in, because GitHub hands each one a different token, a different level of access to your secrets, and in one case no workflow run at all. Once the shape is settled the testing is ordinary; it is the trust boundary that is not.

An agent opens pull requests faster than a person does, and volume is not the only difference. The code in them has been read by nobody, including its author, and the branch may sit in your own repository, in a fork, or behind a bot account with a token of its own. Those are three security positions that look identical in the pull request list.
This page sorts them, says what CI does with each, and quotes the refusal you will meet if you try to shortcut the third.
Three shapes, three trust levels
Sort the pull request before you decide what to run on it. The differences below are set by GitHub, not by your workflow, and the first column is the only thing you have to determine by hand.
| How it arrived | Does a run start? | Token and secrets | What to watch |
|---|---|---|---|
Branch in your repository, pushed with the job's GITHUB_TOKEN | On GitHub.com, yes but approval-required, for opened, synchronize and reopened | Base repository token and secrets, limited by the workflow's permissions | Every other activity type creates no run at all |
| Branch in your repository, pushed with an app token or a personal access token | Yes, like any other push | Base repository token and secrets | The agent now holds a real identity with write access. Scope it |
Fork, through pull_request | Yes, subject to the fork approval policy | Read-only GITHUB_TOKEN, other secrets withheld | Runs left awaiting approval are deleted after 30 days |
Shape one: the agent commits with the workflow's own token
This is the common setup for an agent that already runs inside Actions, and it is the one that surprises people, because the rule is that events triggered by the GITHUB_TOKEN will not create a new workflow run. GitHub.com now carves out pull requests: a workflow using GITHUB_TOKEN that creates or updates one does produce pull_request runs for the opened, synchronize and reopened activity types, in an approval-required state that a user with write access has to release.
Two consequences follow. Your agent cannot conclude anything from a green check that has not started, so it has to read the run's state rather than its absence. And if you want those runs to start unattended, GitHub's own guidance is to create or update the pull request with a GitHub App installation token or a personal access token instead, which moves you to shape two and hands the agent an identity you now have to scope.
Shape two: the agent has an identity of its own
A GitHub App installation token or a personal access token makes the agent an ordinary author: its pushes trigger workflows normally and its pull requests behave like anyone else's. The cost is that the credential is real and it is yours. Give it the narrowest permissions that let it open a pull request, keep it out of workflows that run fork code, and make sure it cannot approve its own pull requests.
This is also the shape where branch protection does the work. Required status checks are the mechanism that stops an unreviewed agent branch from merging, and they apply to an agent exactly as they apply to a person, which is the point.
Shape three: the fork, and the boundary that protects you
A fork pull request is the most defended case, and the defense is deliberate. GitHub documents that pull_request runs the workflow file from the merge commit of the pull request, which for a fork is a commit controlled by someone without write access, so it restricts these events to a read-only GITHUB_TOKEN, withholds access to other secrets, and applies fork approval policies to prevent compute abuse.
That has a practical edge for agent work. A pull request that edits .github/workflows is editing its own examiner, because the workflow that runs is the one in the merge commit. Review workflow changes in an agent branch as carefully as you review the code, and more carefully than you review the lockfile.
The pull_request_target shortcut, and the refusal you will meet
Sooner or later someone wants the fork's code and the repository's secrets in the same job, and reaches for pull_request_target. GitHub's reference describes the difference exactly: with that event, the workflow and any actions/checkout call that does not specify a ref are taken from the base repository's default branch, so no code from the fork runs by default. The hole is opened by the author who points the checkout at the pull request head and then runs it.
Since July 2026 the action refuses to help. actions/checkout throws before fetching when the event is pull_request_target or workflow_run, the head repository is not the base repository, and the resolved repository, ref or commit points at that fork pull request head.
Read the direction before you act on it. This error comes from the action, not from GitHub, and only on a fork pull request: a branch in your own repository takes an early return and checks out normally. So seeing it tells you the head repository is a fork before you have looked at anything else. The opt-out is an input named to be conspicuous in review, allow-unsafe-pr-checkout: true, and the guard ships in actions/checkout v4.4.0, v5.1.0, v6.1.0 and v7.0.1, all published on 2026-07-20.
Refusing to check out fork pull request code from a 'pull_request_target' workflow. This workflow runs with the base repository's GITHUB_TOKEN, secrets, default-branch cache scope, and runner access. Fetching and executing a fork's code in that trusted context commonly leads to "pwn request" vulnerabilities. To opt in, review the risks at https://gh.io/securely-using-pull_request_target and set 'allow-unsafe-pr-checkout: true' on the actions/checkout step.What to actually run on an agent's pull request
Once the shape is settled the suite is the suite, but three habits are worth the minutes. Run the whole thing rather than a selected subset: test-impact selection assumes the author knew what they changed, and an agent's idea of the blast radius is a guess it made before it started editing.
Run new tests more than once. An agent writes tests as well as code, and in our experience a test that has passed exactly once is not yet evidence of anything, least of all when it was written by the same process that wrote the code it is checking. A second run on a fresh machine is cheap next to a flaky test merged into the default branch.
And read the diff for the files CI itself depends on. Workflow files, the lockfile, the Dockerfile and the test configuration change what the run means, not just what it does.
Catch it before the pull request exists
Every shape above is a way of testing code that is already public inside your repository. The cheaper loop is one branch earlier: have the agent run the suite on a clean machine before it opens anything, so the pull request that arrives has already passed somewhere that is not the agent's laptop or its own container.
That is what a one-off remote job is for. latchkey run executes the command on a fresh runner and exits with the job's own code, so the agent gets a verdict it can branch on rather than a web page to read, and running CI from Claude Code covers the three routes side by side. Nothing about it replaces the pipeline: it moves the first failure to a point where nobody else has to see it.
No recorded run backs this page
This page makes no repair claim, and it cannot: content/heal-evidence.mjs has no record for this slug and content/repro-evidence.mjs has no captured run for it, so there is no self-heal block and no reproduction log here.
It would be reproducible, unlike the other two pages in this batch, and that is worth being honest about. The checkout refusal could be captured by opening a fork pull request against a repository with a pull_request_target workflow that checks out the head, and it has not been. What is committed instead is the branch that throws it, the version tags that carry it, and the date those tags were published, so the claim can be checked in the source rather than taken on trust.
Key takeaways
- Sort by how the pull request arrived: the token and the secrets follow from that, not from your workflow.
- On GitHub.com, pull requests opened by a workflow using
GITHUB_TOKENcreate runs that wait for a human to approve them. - A fork pull request gets a read-only token and no other secrets, and
pull_requestruns the workflow from the merge commit. actions/checkoutnow refuses a fork head checkout underpull_request_targetunless you setallow-unsafe-pr-checkout: true.
Frequently asked questions
Do workflows run on pull requests opened by a bot?
GITHUB_TOKEN now produces pull_request runs on GitHub.com for the opened, synchronize and reopened activity types, but they start in an approval-required state and someone with write access has to release them. A pull request opened with a GitHub App token or a personal access token triggers workflows normally.Why does actions/checkout refuse to check out a fork pull request?
pull_request_target or workflow_run and the head repository differs from the base, so a branch in your own repository is unaffected. If you have confirmed the code is never executed, the documented opt-in is allow-unsafe-pr-checkout: true.Can a pull request from a fork read my repository secrets?
pull_request. GitHub restricts that event to a read-only GITHUB_TOKEN, withholds access to other secrets, and applies fork approval policies, precisely because the workflow being run comes from a commit the fork controls. Secrets become reachable only when a workflow opts into an elevated event such as pull_request_target, which is why that event carries the warnings it does.