# ci\(relay\): deploy labeled PRs to relay preview stages · PR \#23872 · pingdotgg/t3code

[View on GitCafe](https://git.cafe/pingdotgg/t3code/pull/23872)

Repository: [pingdotgg/t3code](https://git.cafe/pingdotgg/t3code)

Visibility: public

State: merged

Source repository: pingdotgg/t3code

Source branch: t3/plan-relay-pr-stages

Target branch: main

Head commit: 91874283c2d98a56587d20cbd2a2f6c40349877d

[Discussion and reviews](https://git.cafe/pingdotgg/t3code/pull/23872)

[Commits](https://git.cafe/pingdotgg/t3code/pull/23872/commits?format=markdown)

[Files changed](https://git.cafe/pingdotgg/t3code/pull/23872/diffs?format=markdown)

## Description

---

## Change

Labeling a same-repository PR `relay:preview` deploys Alchemy stage `pr-<n>` (`.github/workflows/relay-preview.yml`). The job runs PR code with account credentials, so it waits for approval in the `relay-preview` environment. It re-checks that the PR is still open and labeled before deploying. Fork PRs are skipped.

Removing the label or closing the PR runs `alchemy destroy` for the stage (`.github/workflows/relay-preview-cleanup.yml`). That workflow runs from the default branch under `pull_request_target`, so no PR code runs and no approval is needed.

Managed endpoint tunnels and their CNAMEs are created by the Worker at runtime and Alchemy does not track them. `PreviewTunnelSweep` (`infra/relay/src/previewTunnelSweep.ts`) is a stack resource, present only on `pr-<n>` stages. Its `delete` removes the stage's tunnels and CNAMEs during `alchemy destroy`. It depends on the Worker, so it runs before the Worker is deleted. A live Worker could create a tunnel in that gap; that window is accepted.

A nightly job destroys stages of closed labeled PRs, one matrix job per PR in the same concurrency group as that PR's deploys. Each job rechecks that the PR is still closed and unlabeled first. Listing is paginated, and a failed listing fails the job.

`previewStages.ts` holds the matching rules. A stage `pr-4` never matches tunnels of `pr-42`.

## Scope and approval

Requested by the maintainer (Julius Marminge) in the T3 Code thread for this change.

Setup outside this PR, already done: the `relay:preview` label, the `relay-preview` environment with the `ping-devs` reviewer rule, the `relay-preview-cleanup` environment with no reviewers, and both environments' secrets and variables.

## Verification

Focused checks run locally on the changed files:

- `vp lint` on the four changed relay TypeScript files: clean.
- `vp run typecheck` in `infra/relay`: 0 errors.
- `vp test run src/previewStages.test.ts`: 4 of 4 pass.
- The sweep listing block was run under `bash -e` against stub `gh` commands: a failing listing exits 1, and a full page exits 1 at the limit.

Not yet verified against live Cloudflare or PlanetScale. The first real run should be a throwaway PR: label it, approve the deploy, confirm the `pr-<n>` stage and its PlanetScale branch, then close it and confirm teardown.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

