
The Pain.
Every time a pull request crosses oceans, the review clock resets. Dallas pushes code at 5 p.m., while London’s still asleep; Sydney won’t see it until tomorrow, and by then, the context is stale. GitHub’s Octoverse shows that almost half of the total cycle time is just idle limbo. While the code cools, developers switch contexts, defects sneak in, and the team’s Joy Index drops from “motivated 8 / 10” to “frustrated 6 / 10."
The Bet.
We’re betting that a strict hand-off rule; “If a PR isn’t green by the next sunrise, wake the next hub”, reduces review time to under 24 hours and boosts morale. A bot tags the on-call reviewer in the current time zone, posts a Slack reminder, and escalates again if two hours pass without a response. The expected result: code reviews happen while the changes are still fresh, context remains clear, and the Joy Index rises again to eight. Cognitive load can affect teams even when they are simply trying to gain context from where the other team left off.
Proof in One File.
review_handoff.py
knits everything together: it retrieves open PRs from GitHub, flags any older than 12 hours without a reviewer, provides a “needs-review” label, and pings Slack with the ticket numbers and an @on-call handle. On the same run, it logs median review latency and the latest Joy score to the Flow-to-Joy radar. One 60-line script, one hourly cron, and we will know in real time whether the discipline of finishing by dawn speeds up flow and boosts team energy—no extra dashboards, no manual data chase.
What we wire
GitHub Action below checks every open PR at 06:00 local, labels those older than 12 h and still unreviewed.
Slack notifier posts a “Review by dawn” alert, tagging the on-call dev in the next time-zone
Radar updater logs median review latency + Joy Index to the four-axis chart we introduced Sunday.
#!/usr/bin/env python3
"""
review_handoff.py — Escalate stale pull-requests to the next time-zone
• Finds open PRs older than THRESH hours with no reviewers assigned
• Adds a “needs-review” label
• Posts an alert to Slack tagging the on-call reviewer group
Run from cron at 06:00 local on each hub machine/container.
"""
import os, sys, time
from datetime import datetime, timezone, timedelta
from github import Github # pip install PyGithub
from slack_sdk import WebClient # pip install slack_sdk
from slack_sdk.errors import SlackApiError
# --- Config via env vars ------------------------------------------------------
GITHUB_TOKEN = os.getenv("GITHUB_TOKEN")
SLACK_TOKEN = os.getenv("SLACK_BOT_TOKEN")
REPO_NAME = os.getenv("GITHUB_REPO", "big-agile/example-repo")
SLACK_CHANNEL = os.getenv("SLACK_CHANNEL", "#pr-review")
ON_CALL_HANDLE = os.getenv("ON_CALL", "@oncall-devs")
THRESH = int(os.getenv("STALE_HOURS", "12")) # hours
# --- GitHub: find stale PR numbers -------------------------------------------
def find_stale_prs():
gh = Github(GITHUB_TOKEN)
repo = gh.get_repo(REPO_NAME)
stale = []
cutoff = datetime.now(timezone.utc) - timedelta(hours=THRESH)
for pr in repo.get_pulls(state="open", sort="created", direction="asc"):
age_hrs = (datetime.now(timezone.utc) - pr.created_at).total_seconds() / 3600
if age_hrs < THRESH:
break # list is sorted; newer PRs won't be stale
if not pr.requested_reviewers and not pr.labels:
pr.add_to_labels("needs-review")
stale.append(pr.number)
return stale
# --- Slack: send escalation message ------------------------------------------
def notify_slack(pr_numbers):
client = WebClient(token=SLACK_TOKEN)
pr_list = ", ".join(f"#{n}" for n in pr_numbers)
text = f":rotating_light: PRs {pr_list} need review! Handoff to {ON_CALL_HANDLE}"
try:
client.chat_postMessage(channel=SLACK_CHANNEL, text=text)
except SlackApiError as e:
print(f"Slack error: {e.response['error']}", file=sys.stderr)
# --- Main flow ----------------------------------------------------------------
if __name__ == "__main__":
prs = find_stale_prs()
if prs:
notify_slack(prs)
print(f"Escalated PRs {prs} for review.")
else:
print("No stale PRs found.")
Flow-to-Joy Radar Template
Feed these two metrics (median review latency, average Joy Index) into the spreadsheet you used Sunday. The moment the green Flow line creeps inward and the orange Joy line dips, you’ll see the gap.
Metric | Healthy Target | Alert Trigger |
---|---|---|
Median PR Latency | < 24 h | ≥ 36 h |
Joy Index | ≥ 8 | < 7 |

A quick glance tells you if speed and morale travel in tandem. The green Flow line tracks hard-edge delivery, cycle-time, deploy frequency, while the coral Joy line measures team energy; overlap near the outer rings means you’re shipping fast and people feel good. When Joy bows inward but Flow stays wide, the team is sprinting tired; when Flow sags but Joy stays high, work feels pleasant yet customers wait.
Coach the squad to pick one experiment per retro based on the gap. Low Joy? Slash after-hours reviews or pair on stubborn PRs. Lagging Flow? Cap WIP and automate reviewer pings. Re-plot next sprint; the goal is two lines hugging each other at 8+ across all axes.