Skip to content

Review SLA

Review SLA #257

Workflow file for this run

# ---------------------------------------------------------------------------
# Review SLA – nudge reviewers when PRs wait too long for review
# Runs every 6 hours and escalates based on wait time.
# ---------------------------------------------------------------------------
name: Review SLA
on:
schedule:
- cron: '0 */6 * * *'
workflow_dispatch:
permissions:
pull-requests: write
issues: write
jobs:
review-sla:
name: Check review SLA
runs-on: ubuntu-latest
steps:
- name: Enforce review SLA
uses: actions/github-script@v8
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const MS_PER_HOUR = 60 * 60 * 1000;
const SLA_24H = 24 * MS_PER_HOUR;
const SLA_48H = 48 * MS_PER_HOUR;
const SLA_72H = 72 * MS_PER_HOUR;
const MARKER_24 = '<!-- review-sla-24h -->';
const MARKER_72 = '<!-- review-sla-72h -->';
const now = Date.now();
// List all open PRs
const prs = await github.paginate(github.rest.pulls.list, {
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
});
for (const pr of prs) {
// Only check PRs that have review requests pending
if (!pr.requested_reviewers?.length && !pr.requested_teams?.length) {
continue;
}
// Determine how long the PR has been waiting
// Use the PR creation time as the baseline
const createdAt = new Date(pr.created_at).getTime();
const waitMs = now - createdAt;
// Check existing reviews – if any review exists, skip
const { data: reviews } = await github.rest.pulls.listReviews({
owner: context.repo.owner,
repo: context.repo.repo,
pull_number: pr.number,
});
if (reviews.length > 0) {
continue; // Already has at least one review
}
// Fetch existing bot comments to avoid duplicates
const { data: comments } = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
});
// --- 72h escalation ---
if (waitMs >= SLA_72H) {
const already72 = comments.some(c => c.body.includes(MARKER_72));
if (!already72) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
body: `${MARKER_72}\n🚨 **Review SLA exceeded (72 h)** — This PR has been waiting for review for over 72 hours. Please prioritize.\n\n@team — urgent review needed.`,
});
}
// Ensure the attention label is present
try {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
labels: ['status/needs-attention'],
});
} catch (_) { /* label may already exist */ }
continue;
}
// --- 48h – add label ---
if (waitMs >= SLA_48H) {
try {
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
labels: ['status/needs-attention'],
});
} catch (_) { /* label may already exist */ }
}
// --- 24h – first nudge ---
if (waitMs >= SLA_24H) {
const already24 = comments.some(c => c.body.includes(MARKER_24));
if (!already24) {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: pr.number,
body: `${MARKER_24}\n⏳ **Review reminder** — This PR has been waiting for review for over 24 hours.\n\n@team — this PR needs a review.`,
});
}
}
}