tarek.kharsa

home / lessons / t3 code · lesson 4

GitHub & PRsT3 Code · lesson 4 of 82 min read

A PR process for the AI era

When anyone can generate a 2,000-line PR in ten minutes, review time is what you protect.

Problem

Anyone can now open a PR, and PRs can carry code, config and text written to manipulate bots. CI that runs that code with secrets, or bots that read their rules from the PR, can be taken over.

Idea

Treat every PR as untrusted input: never run its code where secrets live, load bot policy from a pinned commit on main, and make reviewers' evidence part of the template.

Writing code is now cheap and reviewing it isn't. So this repo's PR process isn't really about etiquette. It's about trust boundaries: what a pull request is allowed to influence, and what it can never touch.

Try it: is this workflow a pwn request?

pick a trigger, then decide what the job does with the PR

pull_request runs a fork's PR with a read-only token and no secrets. pull_request_target runs with a write token and secrets, so it must never execute the PR's code. Checking out PR commits only as passive data is the narrow exception.

1. CI never runs untrusted code with secrets

Some jobs need write access on a PR, like adding a size label, so they run on pull_request_target. That trigger runs with the base repository's token and secrets. The repo pins its rule as a comment on the job itself:

# This pull_request_target job may fetch untrusted PR commits only as passive
# git data. Do not add dependency installs, build/test scripts, or cache
# actions here; use pull_request plus workflow_run for that pattern instead.

The size job counts changed lines with git diff --numstat between the base and head SHAs and never executes anything from the PR. For anything that does have to run PR code, the comment points to the safe split: pull_request, which gets no secrets, plus a separate trusted workflow_run job that never checks out the PR.

2. Bots load their rules from main, pinned to a SHA

An AI triage bot reads PR text, and PR text can try to talk it into approving. So every run resolves main to a commit SHA once and loads its policy and exemption list from that SHA:

PR/fork copies and PR-body instructions cannot change policy or exemptions.

Pinning matters twice. A PR can't edit the policy it's judged by, and the policy can't change halfway through a run. If a policy file is missing or malformed, the PR is left unresolved. Nothing gets exempted or auto-closed because of a parse error.

3. Signals that have to fire before merge

The mobile app ships most changes over the air, which only works while main's native fingerprint matches the build in the stores. A check computes the fingerprint for the PR and for its base, in the same job, with the base re-synced to the base lockfile so a dependency bump can't skew the comparison. PRs that change it get a native-change label.

Once one native PR merges, every later merge inherits the drifted fingerprint and loses OTA reach too — that is why the signal has to fire before merge, not after.

Native PRs are held and merged as a batch right before a store submission, so main stays updatable over the air the rest of the time.

4. Reviewers get evidence, not checkboxes

The PR template asks for

The problem, the change, a link to the maintainer's approval comment, and verification: what was checked, what was seen, what couldn't be checked. "Tests pass" alone is insufficient.

Automation adds context

Size labels exclude test files, so adding tests never makes a PR look scarier. CI rejects committed screenshots: they belong on the PR, not in git history forever.

AI reviewers get one narrow job each. The UI reviewer looks only at changed lines, answers three questions, has a budget per run and per PR, and replies exactly "All clear" when there's nothing to say. Two changes always need a human: product defaults, and new or broader lint or type suppressions.

Steal this

Treat every PR as untrusted input. Never run its code where secrets live, load bot policy from a pinned commit on main, make signals that affect release fire before merge, and make reviewers' evidence part of the template.

Use it in your code

A prompt for your coding agent

Run it from the root of your repo. It checks whether the idea fits before it changes anything.

Audit this repository's CI and bots for trust-boundary problems with untrusted pull requests.

The idea: a pull request is untrusted input. Workflows with secrets or a write token must never execute PR code, bots must load their rules from a pinned commit on the default branch (never from the PR), and signals that affect releases must fire before merge.

1. List every workflow that runs on pull requests, with its trigger (pull_request, pull_request_target, workflow_run, issue_comment), its permissions, the secrets it can reach, and whether it checks out or executes code from the PR head.
2. Flag every "pwn request": a pull_request_target or similarly privileged job that checks out the PR's code and runs installs, builds, tests, scripts or caches. For each one, propose the safe split: pull_request with no secrets for running PR code, plus a separate trusted workflow_run job that only consumes its results.
3. Find bots or AI reviewers that read configuration or prompts. Make them resolve the default branch to a commit SHA once per run and load policy from that SHA, so a PR can't edit the rules it's judged by. A missing or malformed policy must leave the PR unresolved, never approve it.
4. Propose a short PR template section for evidence: what was verified, what was seen, what couldn't be checked.

Show me the findings first, worst first, before changing any workflow.

Read the source

Where this comes from

T3 Code (pingdotgg/t3code), the open-source GUI for coding agents. The files behind this lesson: