Problem
A run launched by a forge webhook against a PR branch (Billy push-back, Vetty guard, Revi review) keeps running — or worse, starts from the queue — after the PR it targets has merged or closed. Observed live on 2026-07-10: a /billy fired on PR #98 stayed queued behind another run; #98 merged meanwhile; the run then started, cloned an empty ref (branch deleted), fell back to the default branch, and churned through a full campaign pass on an empty diff (~7 min of runner time + LLM cost for nothing). With per-team concurrency this orphan also blocks legitimate queued runs.
Two gaps compound:
- At claim time (cheap, high value): a queued run whose subject PR is no longer open should be cancelled instead of started. The launch already records the PR context (
pr_url launch var, webhook delivery SubjectURL); the runner or the queue dispatcher can check PR state before executing.
- On PR terminal events (
pull_request closed/merged deliveries we already receive on the same webhook): look up in-flight runs bound to that PR (match on repo + PR number from the launch metadata) and cancel them (failed/cancelled with a clear reason, checkpoint preserved per the normal cancel path).
Scope / non-goals
- In scope: cloud/webhook-launched runs bound to a PR. Both the claim-time guard and the event-driven cancel.
- Persist the PR binding on the run record if it isn't already queryable (repo + PR number), so the cancel path is an indexed lookup, not a scan.
- Emit a visible event/log line on auto-cancel (
run_cancelled with reason pr_closed), and record it on the webhook delivery trail.
- Non-goal: cancelling operator-launched (studio/CLI) runs — only runs whose provenance is a PR-bound webhook launch.
- Non-goal: GitLab MR parity in the first pass (same seam, can follow).
Acceptance
- A queued run whose PR merged before claim never executes (cancelled with reason).
- A running PR-bound run is cancelled within one poll/event cycle of the PR closing.
- A run NOT bound to a PR is never touched.
- Unit tests for both paths; the PR-closed webhook delivery records the cancel outcome.
Problem
A run launched by a forge webhook against a PR branch (Billy push-back, Vetty guard, Revi review) keeps running — or worse, starts from the queue — after the PR it targets has merged or closed. Observed live on 2026-07-10: a
/billyfired on PR #98 stayedqueuedbehind another run; #98 merged meanwhile; the run then started, cloned an empty ref (branch deleted), fell back to the default branch, and churned through a full campaign pass on an empty diff (~7 min of runner time + LLM cost for nothing). With per-team concurrency this orphan also blocks legitimate queued runs.Two gaps compound:
pr_urllaunch var, webhook deliverySubjectURL); the runner or the queue dispatcher can check PR state before executing.pull_requestclosed/merged deliveries we already receive on the same webhook): look up in-flight runs bound to that PR (match on repo + PR number from the launch metadata) and cancel them (failed/cancelledwith a clear reason, checkpoint preserved per the normal cancel path).Scope / non-goals
run_cancelledwith reasonpr_closed), and record it on the webhook delivery trail.Acceptance