164. Run the shim on pull_request for same-repo PRs
Date: 2026-11-09
Status
Accepted
Supersedes ADR 0009.
Context
ADR 0009 put the PR events of the shim workflow on pull_request_target. The shim is the small .github/workflows/fullsend.yaml in each repository that forwards repository events to fullsend's dispatch workflow. ADR 0009 rejected pull_request because that event runs the PR branch's copy of the workflow, so a PR could rewrite the shim to leak the dispatch token.
Two things have changed since then:
- GitHub now blocks
pull_request_targetby default on public repositories. The 2026-09-17 changelog post on workflow execution protections set enforcement for 2026-11-02; GitHub later moved it to 2026-12-02, with brownouts (two-hour trial enforcement windows) on 2026-11-09 and 2026-11-16. A run without an Actions event policy that allows the event ends instartup_failure, which broke fullsend repositories during the 2026-11-09 brownout (#9221). - The dispatch token no longer exists. The shim now calls
reusable-dispatch.ymlwithworkflow_calland passes repository secrets (ADR 0031, ADR 0062). GitHub App tokens come from fullsend's central token mint (ADR 0029), which accepts only OIDC tokens whosejob_workflow_refnames an allowed workflow host repository, so a rewritten shim cannot point the mint at its own workflow.
What pull_request exposes, by PR author:
| PR author | Workflow file that runs | Secrets | What a rewritten shim could reach |
|---|---|---|---|
| Same-repo branch (author has write access) | PR branch | Yes | Nothing new: a writer can already push a branch whose workflow reads the same repository secrets. The shim's pull_request_review trigger (since #264) already runs the PR branch's copy today |
| Fork | PR branch | No | Nothing: GitHub gives fork runs no secrets and a read-only token |
gh-aw, GitHub's own agentic workflow tooling, made the same move: plain pull_request limited to same-repo PRs by default, with fork PRs handled only after a maintainer's slash command (github/gh-aw#72579).
This ADR aims to keep automatic PR review and retro (the retrospective agent that runs when a PR closes) working on public repositories after 2026-12-02, without asking each repository or organization to add an event-policy exception.
Options
| Option | Fork PRs | Works by 2026-12-02 | Why not chosen |
|---|---|---|---|
A. Keep pull_request_target, allow it with an event policy | Automatic | Yes | Every adopting repository or organization must create and maintain an allow policy for one workflow file. GitHub's default points the other way |
B. pull_request with a same-repo guard; forks via /fs-review | Maintainer command | Yes, as a normal release upgrade | Chosen |
C. pull_request plus a workflow_run follow-up job | Automatic | Yes | Runs the same privileged agent on fork input, just behind another event. Ruled out in #9221 |
D. Platform-service webhook receiver (GitHub App) that authorizes, then starts workflow_dispatch | Automatic, with authorization | No (weeks of work) | Brings back automatic fork review, but needs a hosted receiver. Not decided here |
Decision
We move the shim's PR trigger from pull_request_target to pull_request and run it only for same-repo PRs, so automatic review and retro need no event-policy exception.
- Trigger. The shim listens to
pull_requestwith the types it uses today (opened,synchronize,ready_for_review,closed,labeled,unlabeled). It no longer listspull_request_target. - Same-repo guard, enforced twice. The shim's dispatch job runs a
pull_requestevent only when the PR head repository is the base repository (github.event.pull_request.head.repo.id == github.repository_id). A fork PR runs its own copy of the shim and can delete that guard, so the dispatch workflow also refusespull_requestevents whose head repository differs from the base repository and starts no stage for them. - Fork PRs. A maintainer comments
/fs-reviewto review a fork PR. That runs onissue_commentfrom the base branch, with the authorization of ADR 0054 unchanged. Fork PRs get no automatic retro. - Base checkout. Every checkout in the dispatch workflow takes the base side for
pull_request, as it does today forpull_request_target: the PR's base commit where a checkout pins one, and the base branch where a checkout relied on the event default (onpull_requestthat default would be the PR merge commit). The agent reads the PR diff as data and never runs PR code in a job that holds secrets. - Old callers. The dispatch workflow and CLI keep accepting
pull_request_targetand print a warning that asks the caller to upgrade the shim. - Rollout. The new shim ships as a required change in the next fullsend release, together with the dispatch and CLI changes, so no repository can run the new shim against a dispatch that would check out PR code. Repositories receive it through their normal upgrade (
fullsend repos installre-renders the shim and its pin in one commit).
Forge scope
- GitHub: applies; this is a GitHub Actions trigger choice.
- GitLab: unaffected. GitLab dispatch uses the protected-branch webhook trigger and poller of ADR 0125.
- Forgejo: unaffected; Forgejo has no fullsend shim today.
Consequences
- Repositories that take the release keep automatic review and retro for same-repo PRs after 2026-12-02, with the same secrets, mint tokens and review App as today, so inference setup and where reviews appear do not change; public repositories that do not upgrade lose both, while
/fs-*commands, triage and the fix loop keep working. - Fork PRs lose automatic review and retro until a maintainer comments
/fs-review, as in gh-aw; the webhook receiver of option D stays open as a follow-up to restore automatic fork review. - Retro on
closednow runs only for same-repo PRs, whose authors already have write access, so an external author closing their own fork PR no longer triggers retro. - Dependabot-triggered
pull_requestruns get only Dependabot secrets, so automatic review of Dependabot PRs can fail and a maintainer comments/fs-reviewinstead. - The base-commit checkout and the fork refusal in the dispatch workflow become its security invariants, in place of ADR 0009's "the shim never checks out PR code".
