Standup: “No blockers.” The pull request timeline: opened Tuesday, first review request routed to a team, no human response until Friday. Both statements are true. That is the problem.
This is a postmortem for a class of incident that never gets declared: the review queue that everyone reports as empty because nobody is measuring it. The artifact trail is already there. You just have to read it.
Symptom
Engineers report no blockers. Delivery dates slip anyway. The gap between “work complete” and “merged” grows, but it does not appear in any status report because status reports are self-assessed and the queue is invisible to the person standing in it.
The failure mode is not lying. It is a measurement boundary problem. Standup asks a person a question about their own state. The bottleneck lives at a system boundary between people, where no individual owns the wait.
Timeline
Reconstruct it from artifacts, not memory.
- PR opened. GitHub automatically requests review from code owners when a pull request modifies code they own, per GitHub’s CODEOWNERS documentation. The request is generated. Whether a human sees it is a separate question.
- Review requested, no response. The review sits in
REQUESTEDstate. The PR author’s local work is done. From the author’s seat, there is no blocker — they are waiting on someone else, which they classify as normal latency, not a blocker. - Author pings in Slack. Not logged as an artifact unless you keep the channel. The ping is the real signal that the queue is stuck, and it is the one signal most teams do not retain.
- Review submitted. The Pull Request Reviews API returns reviews in chronological order with a
stateand asubmitted_attimestamp. That timestamp is the end of the wait. The start is the review request event, which lives in the PR timeline, not the reviews endpoint. - Changes requested. State flips to
CHANGES_REQUESTED. The clock resets. A second wait begins. - Approved and merged. Total elapsed time from open to merge is the number that never made it into standup.
Contributing factors
1. The question is wrong
“Any blockers?” asks about the individual’s ability to proceed. A reviewer who has not looked at the PR is not blocking the author’s ability to work — the author has already finished. The wait is real; the framing hides it.
2. CODEOWNERS routes requests, not attention
GitHub’s documentation is explicit: code owners are automatically requested for review when a PR modifies code they own, and code owners are not automatically requested for draft pull requests. When a draft is marked ready, code owners are notified. That is routing. It says nothing about whether the owner has capacity, is on call, or is on leave.
Two details matter for diagnosis. First, if a CODEOWNERS line contains invalid syntax, that line is skipped — meaning a typo can silently remove an owner from the routing table. Second, if you specify a user or team that does not exist or has insufficient access, no code owner is assigned. A PR can therefore be “waiting on review” with no reviewer ever requested. That is not a slow reviewer. That is a broken routing rule.
3. Required reviews create a deadlock surface
Repository administrators can require approvals before merge, and can require review from code owners. When reviews from code owners are required, an approval from any one of the listed owners is sufficient — approvals from all are not required. That is a relief valve. But if the only listed owner is a single person or a team with one active member, the approval gate becomes a single point of failure. The PR is not blocked by a person’s judgment. It is blocked by an org chart.
4. CI retries mask a second queue
Workflow run logs record whether a run succeeded, failed, canceled, or was neutral, and they record the time each step took. They also record “Set up job” and “Complete job” steps that GitHub adds to every job. If your PRs routinely show three or four re-runs before green, the review wait is not the only hidden queue. The CI queue is the other one, and it is visible in the logs if anyone looks.
One caveat from the same documentation: when you download the log archive for a workflow that was partially re-run, the archive only includes the jobs that were re-run. To get a complete picture, you must download the log archives for the previous run attempts. Teams that pull one archive and call it the history will undercount retries.
5. The standup is a self-report, and self-reports are lossy
DORA’s capability research treats visibility of work in the value stream and work-in-process limits as capabilities that drive delivery performance. A standup that asks each person for a verbal status is not a value-stream view. It is a set of independent self-assessments. The queue between people does not appear in any of them.
What the artifacts actually show
Pull the data before you pull the person aside.
| Artifact | Signal | What it can and cannot tell you |
|---|---|---|
| CODEOWNERS file | Routing rules, invalid lines, single-owner paths | Shows who should be requested. Does not show capacity or availability. |
| PR timeline | Review request event, draft-to-ready transition | Shows when the clock started. Does not show whether the reviewer saw the notification. |
| Pull Request Reviews API | state and submitted_at per review, chronological |
Shows when the wait ended. Does not show the start of the wait by itself. |
| Workflow run logs | Success/failure/canceled/neutral, per-step timing, re-run history | Shows CI retries and queue time. Partial re-runs require multiple archives. |
| On-call swap logs | Reviewer availability during the wait window | Explains why a request went unanswered. Does not excuse a missing backup owner. |
The reviews endpoint gives you the end timestamp. The PR timeline gives you the start. Subtract. That number is your review latency, and it is the number that never appears in standup.
Action items
Action item 1: Measure review latency from artifacts, not memory
For a sample of merged PRs, compute time from review request to first review submission. Use the PR timeline for the request event and the reviews endpoint for the submission. Do not ask engineers to estimate. Estimates of waiting are systematically low because waiting is not memorable work.
Action item 2: Audit CODEOWNERS for single points of failure
List every path with exactly one owner or one team. For each, ask: what happens when that owner is on leave, on call, or in a different time zone? If the answer is “the PR waits,” you have found a structural bottleneck. Add a second owner or a fallback team. This is a reversible change with a clear rollback: revert the CODEOWNERS commit.
Action item 3: Check for silently skipped lines
GitHub highlights errors when you navigate to the CODEOWNERS file, and a list of errors is accessible via the API. Run that check. A skipped line is a routing failure that looks like a slow reviewer.
Action item 4: Separate CI queue time from review queue time
Pull workflow run logs for the same sample. Record time from PR open to first workflow start, and count re-runs per PR. If CI queue time is a meaningful fraction of total lead time, the review conversation is a distraction. Fix the queue you actually have.
Action item 5: Change the standup question
Stop asking “any blockers?” Ask “what are you waiting on, and who owns it?” The second question surfaces cross-boundary waits. It also produces a name, which is the only thing that makes a wait actionable.
What this method cannot tell you
Artifact-based diagnosis has confounders. A long review latency might mean a slow reviewer, a missing reviewer, a PR that was too large to review quickly, a holiday, an incident that consumed the reviewer’s week, or a CODEOWNERS rule that routed to the wrong team. The artifacts narrow the hypothesis space. They do not close it.
Git history and PR metadata also reflect process, not intent. A team with a healthy review culture and a team with a broken one can produce similar timestamps for different reasons. Treat the numbers as a starting point for a conversation, not a verdict about a person.
The one thing the artifacts do reliably is contradict the standup. When the log says “waiting on review” and the standup says “no blockers,” the log is the more useful document.
FAQ
Can the Pull Request Reviews API tell me how long a PR waited for review?
Not by itself. The reviews endpoint returns reviews in chronological order with a state and a submitted_at timestamp. That gives you the end of the wait. You need the review request event from the PR timeline to get the start. Combine the two.
Why did no reviewer get requested on my PR?
Check three things. First, the CODEOWNERS file must be on the base branch of the pull request for code owners to receive review requests. Second, if a line contains invalid syntax, that line is skipped. Third, if you specify a user or team that does not exist or has insufficient access, no code owner is assigned. Any of these produces a PR that looks like it is waiting on a reviewer who was never asked.
Does requiring code owner review mean every owner must approve?
No. GitHub’s documentation states that when reviews from code owners are required, an approval from any one of the owners is sufficient. Approvals from all listed owners are not required. The risk is not multiple approvals; it is a single listed owner with no backup.
How do I get complete CI retry history?
Download the log archives for every run attempt, not just the latest. When a workflow is partially re-run, the archive for that run only includes the jobs that were re-run. The earlier attempts hold the rest.
Is review latency a DORA metric?
DORA’s published capability set includes lead time for changes and visibility of work in the value stream, among other capabilities. Review latency is a component you can measure from repository artifacts; it is not a standalone DORA metric. Use it as a local diagnostic, not a benchmark against published research.









