Files

5.7 KiB

Stale PR Follow-up for Requested-Changes Reviews — Product Spec

Linear: none provided (originates from warpdotdev/oz-for-oss#457) Figma: none provided. GitHub automation with no UI beyond PR comments and the closed state.

Summary

External-contributor PRs that receive a requested-changes review — from Oz or from a human — can sit indefinitely with no author follow-up, creating review backlog and giving contributors no clear expectation of when an inactive PR will be closed. This adds an inactivity lifecycle: after a period of no meaningful activity, automation posts reminder comments tagging the author, and if inactivity continues, closes the PR with an explanatory comment. Any new meaningful activity resets the timer.

Problem

Oz turns a non-member REJECT verdict into a real REQUEST_CHANGES review, and humans can request changes directly. Nothing follows up afterward. The Ossie stale-PR report covers the complementary case (a PR waiting on an internal reviewer); this lifecycle owns the other half — a PR waiting on an external author after changes were requested.

Scope

  • v1 applies only to external-contributor PRs — fork PRs, already labeled external-contributor on open by .github/workflows/label_external_contributors.yml.
  • Both Oz-authored and human-authored requested-changes reviews are in scope.
  • Member/collaborator-authored PRs are out of scope: Oz only leaves comment-only feedback on them, and reviewer-side staleness is already surfaced elsewhere.

Behavior

  1. Eligibility. A PR is eligible only when all hold: it is open, not a draft, labeled external-contributor, its latest decisive review state is CHANGES_REQUESTED (Oz- or human-authored, not later dismissed or superseded by an approval), and it does not carry the exemption label.
  2. Activity definition (author-driven). Inactivity is measured from the PR's last author-driven event: the most recent of (a) a head-branch push, measured by GitHub's server-recorded push time (so a backdated or force-pushed commit still counts), or (b) a comment by the PR author (including replies on review threads). Comments by maintainers/other third parties, automation-authored comments (including this lifecycle's own reminders), label changes, base-branch updates, and bare updated_at bumps do not reset the timer — the lifecycle exists to chase author follow-up, so a maintainer nudge must not extend the closure clock.
  3. Reminder at 7 days inactivity. Automation posts one reminder comment tagging the author, explaining that changes were requested and that the PR may be closed after continued inactivity, and stating the closure deadline.
  4. Final warning at 10 days inactivity. A second and final reminder is posted, making clear the PR will be closed in ~4 days if no activity occurs.
  5. Close at 14 days inactivity. Automation re-verifies the PR is still eligible (still CHANGES_REQUESTED, not exempt, not draft) and that the day-10 final-warning reminder was already posted in the current window, then posts an explanatory closing comment (pointing the author at how to reopen/continue) and closes the PR. A PR is never closed without a prior final warning — this also makes the first enablement safe for the existing backlog of already-stale PRs, which receive a warning first and close only on a later run.
  6. Activity resets the timer. Any new meaningful event (per invariant 2) resets inactivity to zero and re-arms all reminder stages; an active PR never advances toward closure.
  7. A later approval or dismissal exits the lifecycle. If the requested-changes state is resolved (Oz/human approves, or the review is dismissed) the PR is no longer eligible and receives no further reminders or closure.
  8. Comment authorship. Reminders and the closing comment are posted by the workflow's GitHub Actions identity (github-actions[bot], via the default GITHUB_TOKEN). Authoring them as the oz-for-oss bot — to match other Oz comments on the PR — is a non-functional nicety, explicitly deferred (see the Tech Spec follow-ups), and must not block this work.
  9. No duplicate or repeated actions. At most one comment is posted per reminder stage per inactivity window; re-running the scan does not post duplicate reminders or re-close an already-closed PR. After a timer reset, stages become eligible again for the new window.
  10. Exemption. A maintainer-applied no-autoclose label removes the PR from the lifecycle immediately; no reminders or closure occur while it is present.
  11. Drafts are skipped. Converting a PR to draft removes it from the lifecycle until it is marked ready for review again.

Non-goals

  • Acting on member/collaborator-authored PRs.
  • Reacting to human REQUEST_CHANGES in real time (this is a periodic inactivity sweep, not a webhook trigger).
  • Reopening or re-reviewing PRs after closure (the closing comment explains how the author can ask to reopen).
  • Creating the external-contributor or exemption labels automatically.

Decisions

  • Cadence: reminder at 7 days, final warning at 10, close at 14 — a 4-day buffer after the final warning.
  • Exemption label: no-autoclose.
  • Initial rollout: full reminder-plus-close. The "no close without a prior final warning" rule (invariant 5) makes this safe for the existing backlog of already-stale PRs.

Open questions

  • A PR where the author has already responded and is now waiting on a maintainer still advances toward closure, since only author activity resets the timer (invariant 2). Maintainers can hold such PRs with no-autoclose. Acceptable for v1, or should "author has responded since the latest requested-changes review" pause the lifecycle? Pausing is fairer but lets a token commit dodge closure indefinitely.