feat(cloud): record every PR a box links
Why
The cloud list and sidebar look up a box's PR by cloud_workspaces.branch, the branch the box was created on. Agents move to new branches with one PR each, so every box except the open one showed a stale or missing PR, and only one. Opening the box "fixed" it because only the open box goes through the host fan-out, which knows its live branch.
What
This is part 1 of 2. It records the data; part 2 adds the read and switches desktop and mobile over to it.
- Table:
cloud_workspace_pull_requests (cloud_workspace_id, repository_id, pr_number, linked_at), plus an index onrepository_idso a repository delete doesn't scan it.linked_atis when the box linked the PR, not when it was reported. It's append-only, with one row per PR a box has linked. A row survives its branch being deleted or reused. State still comes fromgithub_pull_requestsby (repository, number), which webhooks keep current. - Box:
startSandboxPullRequestReporterruns only in a sandbox.- The PR runtime already keeps every PR each workspace has linked (
workspace_pull_requestsin host.db, append-only). The runtime announces each new link (onPullRequestLinked), and the reporter posts the PRs it hasn't sent yet: everything on boot, then on each link. Nothing polls. - It covers every repository checked out on the box, archived workspaces included, and retries a failed send with backoff from 5s up to 5 minutes.
- The PR runtime already keeps every PR each workspace has linked (
- API: a new mutation,
cloudWorkspace.reportPullRequests({ id, pullRequests }). The box calls it through host-service's API client with its sandbox credential (the one the egress firewall adds), the same way it callscloudWorkspace.setDescription. It's onSANDBOX_ALLOWED_PROCEDURES.- Like
setDescription, anyone who can see the workspace may call it, and a box only for itself. - It only records PRs on repositories that workspace checked out. A box can't attach another repo's PRs.
- Repeats change nothing. It sends a
cloud_workspacesnudge only when it adds a row. - It answers with the repositories it ignored. A repo renamed or transferred after the box linked a PR is reported under its old name and dropped; that's a known limit, noted in the sandbox notes.
- Like
- Notes:
docs/cloud-sandbox-mismatches.mdgets an entry.
Latency: a PR opened with gh pr create is linked on the PR runtime's next sweep, up to 5 minutes, and reported about a second after it is linked. The "Create PR" button links straight away, because it refreshes the runtime itself.
Migration safety
- Production sizes: checked on a Neon branch of production:
cloud_workspacesis 216 kB with 161 rows,github_repositoriesis 54 MB with about 155k rows. Neither is in the hot list. - No type change; only a new table. The two foreign keys briefly take SHARE ROW EXCLUSIVE on their parents. The new table is empty, so validation is instant.
- Lock order: nothing writes the child table yet, so there are no in-flight inserts to deadlock against.
- Timing: rebased onto main again; the migration is now
0162. Applied to a fresh Neon branch of production (at main's0161) withlock_timeout=5s statement_timeout=60s: it was the only pending migration and took 223ms. The PR's preview Neon branch was deleted so CI recreates it. - No rehearsal under load: the migration locks only small tables that no job holds open (rule 4).
- No tRPC change in this PR: waived on purpose. It adds one new mutation,
cloudWorkspace.reportPullRequests; no existing input or output changes. - Identifier lengths: the primary key has an explicit name (the generated one was 72 characters). The foreign key names follow the existing pattern; 14 existing constraints already exceed 63 characters.
Verification
-
cloudWorkspace.reportPullRequeststests: it records PRs only for the workspace's own repositories and returns the rest inignored, and a box can't report for another workspace. Each fails when its check is removed. The first also fails if the procedure is missing from the sandbox allowlist. - Reporter test: a PR whose report failed arrives on a retry, and a PR already reported is not sent again. It fails without the retry or without the sent tracking.
- Lint is clean. Typecheck shows only better-auth typing errors from this workspace's install, none in files this PR touches. CI's Typecheck is the authority.
- A real box reporting. That needs a host-service release.
Summary by cubic
Records every PR a box links so the cloud list and sidebar no longer show a stale or missing PR when the box moves to a new branch.
cloud_workspace_pull_requestsstores one append-only row per linked PR (workspace, repository, PR number,linkedAt); the box reports them via tRPC as itself, retrying failed sends with backoff up to 5 minutes.- The box reports all unsent PRs on boot and each new link, including from archived workspaces; the API records only PRs for the workspace's own checked-out repositories, clamps a reported
linkedAtto now, refuses reports for deleted workspaces, and nudges clients only when a row is added.
The client read that switches desktop and mobile to this table lands in a follow-up.
Summary by CodeRabbit
-
New Features
- Cloud workspaces now report linked pull requests, including pull requests created from branches outside the configured workspace branch.
- Pull request links are matched to checked-out repositories and retained with their association to the workspace.
- Reporting runs automatically in the background, with batching, periodic updates, and retry handling when communication fails.
-
Documentation
- Added guidance on reporting closed pull requests, repository mismatches, update timing, and host-service requirements.