Fritz Hut | Thoughts & Commentary

Forensic reads of the artifacts your engineering team already generates — and one reversible experiment per diagnosis.

The Offer Decline Is the Most Honest Data Your Pipeline Produces — and Nobody Logs It

The decline is logged. The reason field says “candidate declined.” The requisition reopens. The recruiter gets a note about market conditions. Nobody pulls the timestamps.

That is the failure. The decline event is real. The label attached to it is usually a guess. And the guess gets aggregated into a metric that leadership reads as a verdict on compensation, when the actual signal is sitting in three columns nobody queried: how long the offer sat open, whether the company or the candidate ended the process, and whether the accepted candidate ever showed up.

Symptom

A team reports an 80 percent offer acceptance rate. The number is within the published band. Nothing looks broken. Then a hiring manager notices that two of the last five engineering offers went to candidates who accepted and then reneged before the start date, and a third was logged as a decline when the company pulled the req after a headcount freeze. The 80 percent is arithmetically correct and diagnostically useless.

This is not a data quality problem in the abstract. It is a specific failure mode: the decline reason field is the weakest artifact in the pipeline, and it is the one everyone reads.

Why the decline reason field is the weakest artifact

Three properties make it unreliable.

It is self-reported through a channel that rewards politeness. A recruiter asking a candidate why they declined is asking a person to critique the person who just rejected them, in a conversation that has no future. The answer that comes back is the socially safe one. One practitioner source puts it directly: capture decline reasons through a neutral channel, because a recruiter asking produces a polite answer and a third party asking produces a real one. That is a claim about channel effects, not a measured comparison, but it matches the structure of the interaction.

It collapses distinct failure modes into one label. “Candidate declined” can mean the candidate took a competing offer, the compensation came in below the range discussed in the screen, the role scope shifted between the loop and the letter, the company rescinded the offer, or the candidate accepted and then reneged. These are different system failures with different owners. A single label cannot tell you which lever to pull.

The denominator is a design choice, not a fact. Published benchmarks differ on whether verbal offers count, whether employer-rescinded offers count as declines, and whether internal transfers are in scope. If your definition differs from the benchmark you are comparing against, the gap between the two numbers may be definitional rather than operational.

Timeline: what the record actually shows

Pull the offer-stage duration for every offer in the last four quarters. In one dataset covering 230,000 applications that reached the offer stage, accepted offers averaged about two days in that stage while declined offers averaged about six. That is a correlation, not a controlled result, and it comes from a single vendor’s analysis. But it is the kind of signal that is cheap to check against your own data, because the timestamps already exist in the ATS.

Then pull the withdrawal flag. In the same dataset, employer-rescinded offers accounted for 17 percent of declined technical roles and 27 percent of declined business roles. Between a sixth and a quarter of what the report called a candidate decline was the company changing its mind. That figure comes from one dataset and should not be treated as a universal rate. The structural point is what matters: if your ATS does not distinguish company withdrawal from candidate choice, your decline rate is measuring two different things at once.

Then pull the acceptance-to-start gap. The difference between conditional acceptance and final acceptance reveals how many candidates accept an offer but renege before their start date. Most teams stop measuring at “accepted” and never instrument this. In markets with long notice periods, the gap between yes and day one is where the largest leak sits.

Contributing factors

Blended rates hide segment differences. In an analysis of 230,000 applications that reached the offer stage, technical roles accepted at about 73 percent against 84 percent for business roles. An 11-point gap means a company-wide number describes neither population and will drift purely on hiring mix. If your engineering hiring went up as a share of total hires, your blended rate can fall without any change in how offers are written.

Speed is a signal, not just a delay. A survey of 12,000 candidates across seven countries found 42 percent had left a recruitment process because scheduling an interview took too long. In Greenhouse’s 2025 workforce and hiring report, the most common trigger for a US candidate ghosting an employer was poor communication or long delays from that employer, at 24 percent, followed by a poor interview experience at 23 percent. A long time-to-offer correlates strongly with lower acceptance rates. These are correlations from survey and vendor data, not causal estimates, but they point at process rather than at the offer letter.

Misalignment discovered late reads as a trust problem. One source reports that 72 percent of candidates said the job differed from what was advertised. That figure comes from a secondary summary and should be treated as directional. The mechanism is plausible: when role scope, compensation, or expectations shift between the loop and the letter, the candidate does not wait for clarity.

Counteroffers are a known failure mode with a known window. In one European benchmark table, counteroffers from the current employer are listed at 12 percent of declines and described as most common in Germany and Switzerland, where notice periods give current employers time to react. The specific percentage is from one source; the structural point is that a long notice period is a window in which the current employer can act.

Worked example: reading the artifact

Consider a hypothetical team. Blended acceptance rate: 80 percent. Technical roles: 71 percent. Business roles: 86 percent. The blended number is within the published band and nobody escalates.

Pull the withdrawal flags. Of the 20 percent of offers logged as declines, six of them were employer-rescinded after a budget review. The true candidate-decline rate is lower than reported. The requisition discipline problem is worse than reported.

Pull the offer-stage duration. Accepted offers sat open for a median of two days. Declined offers sat open for a median of seven. The three offers that sat open for more than ten days were all declined. The pattern is visible in data the team already had.

Pull the acceptance-to-start gap. Two candidates accepted and then reneged during a 60-day notice period. Neither was flagged as a decline in the acceptance-rate calculation. The metric that leadership reviews does not include them.

None of this required a new survey. It required querying columns that already existed and separating events that had been collapsed into one label.

Action items

1. Fix the logging before fixing the offer. A metric that mixes company withdrawal with candidate choice cannot tell you which lever to pull. Add a distinct status for employer-rescinded offers. Add a distinct status for accepted-then-reneged. Write down whether verbal offers count. This is a schema change, not a strategy change, and it is reversible.

2. Instrument days-offer-sits-open. This is the cheapest predictive signal in the funnel. It costs nothing to compute from existing timestamps. Track it by segment, by hiring manager, and by recruiter. Do not set a target yet. Just measure the distribution for one quarter.

3. Run one reversible experiment on offer-stage latency. Pick one segment — one team, one role family, one region. Set an internal SLA from hiring-manager decision to verbal call: 48 hours. Keep the old process for a comparison segment. Measure days-offer-sits-open and acceptance rate in both segments for one quarter. If the treatment segment does not move, roll back. This is a process change with a rollback plan, not a claim about what will happen. The sources report correlations between offer-stage duration and outcomes; they do not report a controlled experiment on this specific intervention.

4. Separate the segments before comparing to any benchmark. If technical roles accept at a materially different rate than business roles, report them separately. A blended number that moves with hiring mix is not a performance metric.

5. Capture decline reasons through a channel that is not the recruiter who extended the offer. This is a recommendation, not a finding. The evidence for channel effects is thin. But the structural argument is that the person who delivered the rejection is the worst-positioned person to ask why it was rejected.

What this does not tell you

The 17 and 27 percent figures for employer-rescinded offers come from one dataset and one vendor’s analysis. They are not a universal rate. The two-day versus six-day offer-stage figures come from the same source. The 73 versus 84 percent technical-versus-business gap comes from the same analysis. These are useful as a starting hypothesis, not as a benchmark to adopt.

No source here reports a controlled experiment showing that a three-day offer SLA raises acceptance rates by a specific amount. The recommendation to run the experiment is a recommendation, not a finding.

The cost of a declined offer is sometimes cited at a fixed dollar figure. That figure comes from a single vendor citation and would need independent verification before use in a budget argument.

FAQ

What counts as a decline? That is a design choice. Decide up front whether verbal offers count, whether employer-rescinded offers count as declines, and whether internal transfers are in scope. Write the definition down next to every rate on your dashboard.

Why does the decline reason field matter if it is unreliable? Because it is the field everyone reads. The fix is not to stop collecting reasons. The fix is to stop treating the reason as the primary signal and start reading the timestamps and withdrawal flags that nobody has to narrate.

Is a slow offer really the problem, or is it compensation? Both can be true. In one survey of 2,800 professionals, 17.3 percent of offer declines cited compensation as the primary reason. In a European benchmark table, the top listed reason is accepting a competing offer at 35 percent, followed by compensation below expectations at 28 percent. These are different sources with different methodologies. The point is not to pick one. The point is that a slow process gives competing offers time to reach the same stage, which converts a speed problem into a competing-offer problem.

What is the difference between offer acceptance rate and offer-to-join ratio? Offer acceptance measures whether a candidate says yes. Offer-to-join ratio measures whether they actually start. In markets with long notice periods, the second number is often the real problem, because acceptance can look healthy while candidates drop out during a 60 to 90 day notice window.

Should we extend exploding offers with 24-hour deadlines? The sources here do not support that. A short deadline creates pressure that can produce a regretted acceptance. The recommended window in one source is two to three days, which matches the observed clustering of accepted offers around two days open. A longer window rarely produces a more considered yes; it mostly gives competing processes time to catch up.

How do we know if our decline rate is actually bad? Segment it first. If your technical-role acceptance rate is well below your business-role rate, the blended number is hiding the problem. If a meaningful share of your logged declines are employer-rescinded, your true decline rate is lower than reported and your requisition discipline is worse. Neither of those is visible in the headline number.

The forensic move

The decline is honest data in the sense that it is a real event. The label attached to it is often a guess. The forensic move is to separate the event from the label, pull the timestamps that nobody had to narrate, and run one reversible experiment on the part of the process you can still influence.

Start with the logging. A metric that cannot distinguish company withdrawal from candidate choice cannot tell you which lever to pull. Fix that first. Then measure how long your offers sit open. Then decide what to change.

The Ball at the Bottom of the Ramp: Reading the ‘I Had Potential’ Diagram

Editorial note: This publication and Quirky Cotton Collective share an owner.

The shirt shows a diagram. White capitals: I HAD POTENTIAL. Below the words, a white curved ramp descends from an upper-left ledge to a lower-right flat. A dashed empty circle sits at the top of the ramp. A solid white ball rests at the bottom. The ramp is curved, not a straight diagonal. That curve is the whole joke, and it is also the reason the shirt works as more than a slogan.

Most joke diagrams fail because they cheat. They draw a straight line, put a ball at the bottom, and let the caption do all the work. This one doesn’t. The dashed circle at the upper left is the ball’s starting position — the potential state, the place where the object could still become something. The solid ball at the lower right is the ball after the ramp. Same object, different position. The dashed outline is not decoration; it is the before. The solid fill is the after. The curve is the path between them.

That is a physics diagram, not a metaphor pasted onto a shirt. A ball released on a curved ramp accelerates, follows the surface, and arrives at the bottom with whatever kinetic energy the drop gave it. The diagram doesn’t show the ball mid-ramp. It shows the two stable states: the one where potential is still theoretical, and the one where it has been spent. The caption reads I had potential, past tense, and the drawing agrees. The ball is already at the bottom. The dashed circle is a memory.

I keep coming back to the dashed circle. In engineering diagrams, a dashed outline usually means one of two things: a hidden feature, or a previous position. Here it reads as both. The ball that could have been is still visible, but only as an outline. The solid ball is what remains. That is a more precise joke than most sarcastic shirts manage, because it doesn’t say the potential was wasted. It says the potential was located — at the top of the ramp, in a specific place, before gravity did its work. The shirt is not complaining. It is reporting a result.

I have spent a lot of time reading artifacts that teams generate without meaning to: git logs, retry counts, swap logs, PIP timelines. The signal is rarely in the stated message. It is in the structure. A PIP that lists performance concerns but no measurable prior feedback is not a performance document; it is a paper trail. A retry log with a spike every Tuesday at 2 a.m. is not a flaky test; it is a cron job nobody owns. The I Had Potential diagram has the same quality. The caption tells you one thing. The drawing tells you another. The drawing is more honest.

What the drawing says is that potential is a position, not a property. The ball at the top of the ramp has potential energy because of where it is, not because of what it is. Move it to the bottom and the potential is gone — not because the ball changed, but because the configuration changed. That is a useful distinction for anyone who has watched a promising engineer get promoted into a role with no ramp, or a strong team get reorganized into a configuration where their best work has nowhere to go. The potential didn’t disappear. The ledge did.

I am not going to pretend the shirt is making an argument about engineering management. It is a diagram joke. But it is a well-made diagram joke, and the difference between a well-made diagram joke and a bad one is the same difference between a useful postmortem and a blame session: specificity. The dashed circle is specific. The curved ramp is specific. The solid ball at the bottom is specific. A lesser version of this shirt would have drawn a straight line and a frowning face. This one drew the physics.

If you want the object, it is the I Had Potential shirt from Quirky Cotton Collective. The listing shows the same diagram I have been describing: white capitals, curved ramp, dashed circle upper left, solid ball lower right. The design is minimal enough that the curve reads at a distance, which is more than I can say for most text-heavy shirts. The joke lands whether or not you know the physics, but it lands harder if you do.

The reason I keep looking at it is the same reason I keep pulling artifacts at work. The stated message is rarely the whole message. The caption says I had potential. The diagram says here is where it was, and here is where it went. That is a more interesting sentence, and it is the one the shirt actually prints.

Twelve Thumbs-Up, Zero Written Objections, One Contested Decision

Symptom: A decision merged with twelve approvals and an empty objection field. Two weeks later, someone reopens it in a thread titled “quick question about the thing we already decided.”

Diagnosis: The review instrument was configured to count approvals. It was never configured to surface objections. Twelve approvals and zero objections are not the same measurement, and reading the second as a null result is the error this article is about.

I am going to walk through this the way I would walk through an incident: pull the artifacts, name what each one actually records, find the system boundary, run one reversible experiment. No blame. The people who clicked approve are not the problem. The instrument is.

What each artifact actually records

Three artifacts get conflated in every post-decision argument. They measure different things.

  • The CODEOWNERS file records who was asked to review. It does not record who read the diff.
  • The PR thread records who spoke. It does not record who disagreed and stayed quiet.
  • The approval count records who clicked. It does not record who examined.

If you have twelve approvals and zero written objections, you have data from the third instrument and nothing from the second. That is not a null result. It is an unmeasured variable. Treating an unmeasured variable as zero is the specific failure mode.

The mechanical fact that makes the gap possible

GitHub’s documentation is explicit about how code-owner review works. Code owners are automatically requested for review when someone opens a pull request that modifies code they own. That is the request mechanism. It is not a reading mechanism.

More importantly: when reviews from code owners are required, an approval from any one of the owners is sufficient to meet the requirement. The documentation gives the example directly — if a line reads *.js @global-owner1 @global-owner2, changes to JavaScript files can be approved by either owner, and approvals from both are not required.

So twelve approvals can mean twelve people were asked. It does not mean twelve people examined. The instrument counts the first and is silent on the second.

There is a second mechanical gap worth checking before you do anything else. If a specified user or team does not exist or has insufficient access, a code owner will not be assigned at all. A path with no matching owner falls through. The review request never fires. The approval count still looks healthy because other reviewers were requested by other rules.

Pull your CODEOWNERS file. Find the contested path. Check whether it has an owner that actually resolves. If it does not, you have found the first contributing factor, and it has nothing to do with anyone’s diligence.

“No objection” is not “no objection recorded”

The IETF has been running consensus processes on technical decisions for decades, and RFC 7282 is the document that explains what rough consensus actually means. It is informational — it describes IETF process, not software teams — but the distinction it draws is the one that matters here.

From the RFC: determining consensus, coming to consensus, and having consensus are different things. A chair who asks “Is everyone OK with choice A?” will get objections. A chair who asks “Can anyone not live with choice A?” is more likely to hear only from people who consider the choice impossible to engineer given the constraints.

Read that again as a statement about review prompts. “Approve or request changes” is the first question. It invites a click. “State your objection in writing by Friday or we proceed” is the second question. It invites a sentence.

Silence in a review thread is the answer to the first question. It is not the answer to the second. Any process that reads the empty objection field as consensus is reading the wrong instrument.

Two failure modes that produce the empty objection field

RFC 7282 names both of them, and both look like agreement in the artifact.

Capitulation. A minority objects, gets tired of arguing, and says “forget it, do what you want.” The RFC is blunt about what this is: they have given up trying to appease the others. That is not coming to consensus. There still exists an outstanding unaddressed objection. In the PR thread, capitulation looks like a comment that stops mid-sentence, or a review that was requested and never submitted, or a thumbs-up on a comment that says “I still have concerns but I don’t want to block.”

Horse-trading. “I object to your proposal for such-and-so reasons. You object to mine for this-and-that reason. If you stop objecting to mine, I’ll stop objecting to yours and we’ll put them both in.” The RFC’s verdict: this produces an agreement of sorts, but instead of one outstanding unaddressed issue, it produces two, ignored for the sake of expedience.

Both failure modes leave the objection field empty. Both leave the decision contested. The artifact cannot tell them apart from genuine consensus, because the artifact was never designed to.

The forensic pass

Here is the sequence I would run on a contested decision. It takes about an hour and requires no meetings.

Step one: pull the CODEOWNERS file. Find the path that was changed. Check whether it has a resolving owner. Check whether the owner is a team that still exists and still has write access. If the owner does not resolve, the review request never fired, and the approval count is measuring something else entirely.

Step two: pull the PR thread. Count distinct commenters, not approvals. If twelve approvals came from three commenters, the review was performed by three people and ratified by twelve. Those are different numbers and the difference is worth knowing.

Step three: read the comments for the two failure modes. Look for capitulation language — “I don’t want to block,” “let’s just move forward,” “I’ll raise it later.” Look for horse-trading — paired concessions where two objections cancel each other out. Both are signals that an objection existed and was not recorded as one.

Step four: check the CI history for the same period. Workflow run logs can be searched, and when you search logs, only expanded steps are included in the results. That detail matters more than it looks: a flaky gate that fails and passes on retry trains reviewers to approve without reading, because the gate’s signal has stopped meaning anything. If the contested PR sat behind a job that was retried three times before going green, you have a contributing factor that has nothing to do with the reviewers.

Step five: pull the git log for the affected path. Git log output is given in reverse chronological order by default, which makes it easy to read the sequence of changes to a file. If the contested decision was reversed and re-applied twice in six months, you are looking at a retry storm at the decision level — the same argument re-litigated because the first resolution never addressed the underlying objection.

One reversible experiment

The fix is procedural, not cultural. Change the prompt.

For the next contested decision, replace the approval request with a written objection prompt. Something like: “This decision affects [path]. If you have an objection, state it in this thread by [date]. If no objection is recorded by then, we proceed.”

Name a recipient. Set a deadline. Record the responses in the thread where they can be audited later.

The rollback is trivial. You have lost nothing but a week of false confidence, and you have gained a record that distinguishes “no objection” from “no objection recorded.” If the experiment produces no objections and no re-litigation, you have learned something real. If it produces objections, you have learned something more useful.

This is the same move the SRE literature makes about postmortems. Google’s postmortem culture chapter states it plainly: an unreviewed postmortem might as well never have existed. The document’s presence is not evidence that the process ran. The same is true of an approval. The click’s presence is not evidence that a review happened.

Why this is a system boundary, not a character flaw

The temptation, when a decision gets re-litigated, is to conclude that twelve people were lazy or that one person was cowardly. That conclusion is almost always wrong, and it is expensive because it produces a fix that does not work.

The blameless postmortem literature is relevant here, and it is worth being precise about what it does and does not claim. Google’s SRE book describes blameless postmortems as focusing on contributing causes without indicting any individual or team for bad or inappropriate behavior. Atlassian’s incident management guide describes the point as removing the fear of looking stupid, being reprimanded, or losing a job, in order to encourage honest, objective, fact-centric communication. Atlassian also notes that detractors question whether blamelessness is achievable and whether it undermines accountability. I am not going to pretend that debate is settled. What is settled is the mechanism: if surfacing an objection carries social cost, objections will not be surfaced, and the artifact will show zero.

The failure is not that twelve people were lazy. The failure is that the review instrument was configured to count approvals and never configured to surface objections. That is a configuration. Configurations can be changed.

What to do with this

Pull the artifact. Find the signal. Trace the failure to the system boundary. Run one reversible experiment with a rollback plan.

In this case the artifact is the CODEOWNERS file and the PR thread. The signal is the gap between who was asked and who spoke. The system boundary is the review prompt. The experiment is a written objection prompt with a deadline and a named recipient.

Twelve thumbs-up is not consensus. It is twelve data points from an instrument that was never calibrated to measure disagreement. Stop reading it as if it were.

FAQ

Does this mean approval-based review is broken?

No. It means approval count and objection count measure different things, and conflating them produces false confidence. Approval-based review works fine for changes where the question is “is this correct?” It works poorly for changes where the question is “does anyone have a problem with this?” Those are different questions and they need different prompts.

What if nobody actually objects?

Then you have a real null result instead of an unmeasured variable. That is strictly better information. The experiment costs a week and produces a record you can point to the next time someone asks why the decision was made.

Is RFC 7282 the right model for a software team?

No, and it does not claim to be. It is an informational document about IETF process. What transfers is the distinction between determining consensus and having it, and the two failure modes — capitulation and horse-trading — that produce the appearance of agreement without the substance. The rest of the IETF process is specific to the IETF.

How do I check whether a CODEOWNERS path is actually owned?

Open the file, find the pattern that matches the path, and check that the listed user or team exists and has write access. GitHub’s documentation notes that if a specified user or team does not exist or has insufficient access, a code owner will not be assigned. You can also hover over a file in the repository to see code ownership details from the CODEOWNERS file for the branch you are viewing.

What if the objection prompt produces objections that are not technical?

Then you have learned that the decision has a stakeholder problem, not a technical one, and you can address it as such. That is still better than discovering it two weeks after the merge.

‘No Blockers’ in Standup, ‘Waiting on Review’ in the Logs

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.

  1. 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.
  2. Review requested, no response. The review sits in REQUESTED state. 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.
  3. 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.
  4. Review submitted. The Pull Request Reviews API returns reviews in chronological order with a state and a submitted_at timestamp. 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.
  5. Changes requested. State flips to CHANGES_REQUESTED. The clock resets. A second wait begins.
  6. 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.

The Review Calendar That Rewrites Your Incident History

Symptom: The incident count for last quarter looks flat. The review calendar says you held every postmortem. The git history says otherwise.

Timeline: A team changes its postmortem review cadence from weekly to monthly. Six weeks later, a Sev-2 that would have been reviewed in the old cadence is closed with a Slack thread and a rollback. No postmortem document is created. The incident does not appear in the quarterly reliability report. The review calendar still shows a meeting occurred.

Contributing factors: The calendar is treated as the record. The artifacts that actually preserve incident history — postmortem documents, incident channels, commit messages, CODEOWNERS changes, CI retry logs — are not cross-checked against it. When the cadence changes, the calendar overwrites the history it was supposed to preserve.

This is a predictable failure mode of any review process that relies on a scheduled meeting as its primary evidence. The meeting is a container. The artifact is the record. When the container changes shape, the record leaks.

What the review calendar actually controls

A review calendar does three things, and only one of them is visible on the calendar.

First, it sets the trigger threshold for what gets reviewed. Google’s SRE book describes common postmortem triggers: user-visible downtime beyond a threshold, data loss, on-call intervention, resolution time above a threshold, and monitoring failure. The book is explicit that these criteria should be defined before an incident occurs so everyone knows when a postmortem is necessary. A calendar change that moves review from weekly to monthly does not change the trigger criteria on paper. It changes the practical threshold, because incidents that resolve before the next review slot are more likely to be closed informally.

Second, it sets the review latency. Atlassian’s incident management guidance recommends drafting the postmortem immediately after a post-incident review held within 24–48 hours of resolution, and not more than five business days. The Google SRE Workbook’s case study of a bad postmortem notes that a four-month publication delay meant team members likely forgot key details, and the incident recurred in the interim. Latency is not neutral. It is a lossy compression algorithm applied to operational memory.

Third, it sets who is in the room. The Google SRE book describes a formal review step where senior engineers assess the draft for completeness: whether key incident data was collected, whether impact assessments are complete, whether the root cause is sufficiently deep, whether the action plan is appropriate, and whether the outcome was shared with relevant stakeholders. Change the cadence and you change the reviewer pool. Change the reviewer pool and you change what counts as a complete postmortem.

How the calendar rewrites history

The rewrite is not malicious. It is a side effect of using the calendar as the index and the postmortem as the content. When the index changes, the content that no longer fits the index becomes invisible.

Consider a concrete sequence. A team holds weekly incident review meetings. Every incident above a severity threshold gets a postmortem document, a review slot, and a line in the weekly notes. The calendar is dense. The artifacts are dense. The two match.

Now the team moves to monthly review. The meeting still happens. The postmortem template is still available. But the weekly slot is gone. An incident that resolves on a Tuesday and would have been reviewed on Thursday now waits three weeks. By the time the monthly meeting arrives, the incident channel has been archived, the on-call engineer has rotated, and the rollback commit is buried under three weeks of merges. The postmortem is either written from memory or skipped. The calendar shows a meeting. The artifact record shows a gap.

This is the mechanism. The calendar does not delete the incident. It deletes the review of the incident, and the review is what produces the durable artifact. Without the artifact, the incident is not part of the organizational record. It is a Slack thread that will be lost when the workspace retention policy kicks in.

Artifacts that preserve incident history

If the calendar is unreliable as a record, what is reliable? The artifacts teams already generate. Here is what to pull and what each one tells you.

Postmortem documents

The postmortem is the primary record. Google’s SRE book defines it as a written record of an incident, its impact, the actions taken to mitigate or resolve it, the root cause(s), and the follow-up actions to prevent recurrence. The book also states that an unreviewed postmortem might as well never have existed. That is the standard to hold the calendar to: if the calendar shows a review but no postmortem document exists, the review did not happen in any way that matters.

Pull the postmortem repository and sort by incident date, not publication date. The gap between the two is a latency metric. A postmortem published four months after the incident is not a record of the incident. It is a record of what someone remembered four months later.

Incident channels

The incident channel is the raw log. It contains the timestamps, the hypotheses, the dead ends, and the moment someone said “wait, this looks like the thing from March.” That last one is the signal. If the same failure mode appears in two incident channels and only one has a postmortem, the review calendar has a hole in it.

Search incident channels for the phrase “this is the same as” or “didn’t we fix this.” Each hit is a candidate for a missing postmortem. Each missing postmortem is a candidate for a calendar that is not capturing what it claims to capture.

Git history

Git log is the timeline of what actually changed. The git log documentation describes it as showing commit logs in reverse chronological order by default, with options to limit by date, author, and message pattern. That is enough to reconstruct a change timeline independent of any postmortem.

Pull the commits around the incident window. Look for the rollback commit, the hotfix, the revert. Then check whether a postmortem exists for that window. If a revert commit exists and no postmortem exists, the incident happened and the review process missed it.

The commit message is also evidence. A message that says “revert #1234, fixes prod” is an incident marker. A message that says “revert #1234, see postmortem PM-456” is a linked record. The difference between the two is the difference between an incident that is traceable and one that is not.

CODEOWNERS changes

CODEOWNERS files define who is responsible for code in a repository. GitHub’s documentation states that code owners are automatically requested for review when someone opens a pull request that modifies code they own, and that branch protection can require approval from a code owner before merge. The file is a map of ownership at a point in time.

When a CODEOWNERS file changes, the ownership map changes. If a service’s code owner changes in the same week as an incident, the incident review may have been conducted by someone who no longer owns the code. The postmortem may name an owner who has since been removed. The action items may be assigned to a team that no longer exists.

Pull the CODEOWNERS history and align it with the incident timeline. A CODEOWNERS change during an incident window is a contributing factor to review confusion. It is not the cause of the incident, but it is a cause of the incident being misattributed.

CI retry logs

CI retry logs are the least glamorous artifact and the most honest. A retry is a signal that something failed and someone decided to try again rather than investigate. A cluster of retries around a deploy window is a signal that the deploy was flaky and the team normalized the flakiness.

This is normalization of deviance in its purest form. The alert that fires every Tuesday and gets acknowledged without action is the same pattern. The CI job that fails twice and passes on the third try is the same pattern. Neither generates a postmortem because neither crosses the severity threshold. Both are evidence that the threshold is wrong.

Pull the retry logs for the quarter. Count retries per pipeline. A pipeline with a rising retry rate is a pipeline with a rising incident rate that the review calendar is not capturing.

On-call swap logs

On-call swap logs are the artifact that connects incident history to human history. A swap is a signal. It can mean a vacation, a conflict, or a refusal to carry the pager for a service that pages too often. The last one is the signal that matters.

Pull the swap logs and align them with the incident timeline. A spike in swaps after a series of incidents is a retention risk. A spike in swaps before a known incident is a leading indicator. Neither appears in the postmortem. Both appear in the swap log.

PIP timelines and exit interview notes

This is where the review calendar does the most damage. A performance improvement plan that starts two weeks after an incident is a data point. An exit interview that mentions on-call burnout is a data point. Neither is a postmortem. Both are evidence that the incident review process failed to surface a system problem and the problem was attributed to a person instead.

The Google SRE book is explicit on this point: blameless postmortems focus on identifying contributing causes without indicting any individual or team, because a culture of finger pointing leads people to withhold information. The book states that you cannot fix people, but you can fix systems and processes. When a PIP timeline aligns with an incident timeline, the question is not whether the person failed. The question is whether the system failed to give the person what they needed.

Pull the PIP start dates and the incident dates. Align them. A PIP that starts within 30 days of an incident that has no postmortem is a misattribution waiting to be documented.

How to run the forensic pass

This is a procedure, not a philosophy. It takes one afternoon and produces a list of gaps.

Step 1: Pull the calendar. Export the review meeting invites for the last two quarters. Note the cadence, the attendees, and the dates.

Step 2: Pull the postmortems. Export the postmortem documents for the same period. Sort by incident date. Note the publication date for each.

Step 3: Align the two. For each review meeting, list the postmortems discussed. For each postmortem, list the review meeting where it was discussed. The gaps are the incidents that have a review slot but no postmortem, and the postmortems that have no review slot.

Step 4: Pull the git log for the same period. Use git log --since and git log --until to bound the window. Search for revert commits, hotfix commits, and commit messages that mention production. Each one is a candidate incident.

Step 5: Cross-check. For each candidate incident from git, check whether a postmortem exists. For each postmortem, check whether a revert commit exists. The mismatches are the rewrites.

Step 6: Pull the CODEOWNERS history. Use git log --follow CODEOWNERS to see the ownership changes. Align them with the incident timeline. Note any incident where the code owner changed during the incident window.

Step 7: Pull the CI retry logs. Count retries per pipeline per week. Plot the trend. A rising trend is a leading indicator of incidents that the review calendar is not capturing.

Step 8: Pull the on-call swap logs. Count swaps per week. Align with the incident timeline. A spike in swaps is a signal that the on-call load is not sustainable, regardless of what the postmortem says.

Step 9: Pull the PIP timelines and exit interview notes. Align the start dates with the incident dates. Note any PIP that starts within 30 days of an incident with no postmortem.

Step 10: Write the gap report. The report is not a postmortem. It is a list of incidents that the review calendar missed, with the artifact that proves each one. The report goes to the same people who own the review calendar.

One reversible experiment

The experiment is this: add a second review slot, not a replacement. Keep the existing cadence. Add a 15-minute weekly slot whose only agenda item is “what happened this week that we did not review.” The slot has no postmortem requirement. It has one output: a list of incidents that did not cross the threshold.

The rollback plan is simple. If the slot produces no items for four consecutive weeks, cancel it. If it produces items, the items are the evidence that the threshold is wrong. The threshold can then be adjusted with data rather than opinion.

This experiment is reversible because it adds a meeting, not a process. It does not change the postmortem template. It does not change the severity definitions. It does not change the review calendar. It adds a sensor. The sensor either reads something or it does not. Either outcome is information.

The experiment also has a second output: the list of incidents that did not cross the threshold is a list of candidates for the forensic pass described above. The weekly slot is the intake. The forensic pass is the analysis. The two together are the mechanism for detecting when the review calendar is rewriting the incident history.

What to do with the gap report

The gap report is not a blame document. It is a map of where the review process is not matching the operational reality. The map has three uses.

First, it corrects the record. The incidents that were missed are added to the incident repository with a note that they were identified retroactively. The note is important because it distinguishes a postmortem written at the time from a postmortem written after the fact. The Google SRE Workbook’s case study of a bad postmortem is explicit that delayed publication loses detail. A retroactive postmortem is better than no postmortem, but it is not the same artifact.

Second, it corrects the threshold. The incidents that were missed are the data points that define the new threshold. If the weekly slot produces five incidents in a month that did not cross the old threshold, the old threshold was wrong. The new threshold is defined by the data, not by the calendar.

Third, it corrects the attribution. The PIP timelines and exit interview notes that align with missed incidents are the evidence that the system failure was misread as a personal failure. The gap report does not reverse a PIP. It does provide the context that the PIP process did not have.

Why this matters

The review calendar is a coordination mechanism. It tells people when to show up and what to bring. It is not a record. When it is treated as a record, it overwrites the artifacts that are. The overwrite is silent because the calendar still shows a meeting. The meeting still happened. The postmortem did not.

The fix is not to abolish the calendar. The fix is to stop using it as the index. The index is the artifact. The calendar is the schedule. When the two disagree, the artifact wins. That is the rule. Everything else is a process detail.

The rule has a corollary: if the artifact does not exist, the incident did not happen in any way that the organization can learn from. The calendar cannot fix that. Only the artifact can.

FAQ

Does this mean every incident needs a postmortem?

No. Google’s SRE book is explicit that the postmortem process has an inherent cost and that teams are deliberate in choosing when to write one. The point is not to postmortem everything. The point is to know which incidents were not postmortemed and why. The gap report is the record of that decision. Without it, the decision is invisible and the calendar becomes the only record.

What if the review calendar is working?

Then the forensic pass will show that the postmortems and the calendar align. That is a useful result. It means the review process is capturing what it claims to capture. The pass is not an accusation. It is a verification. Verification that passes is still verification.

How do I get the artifacts if the team does not keep them?

Start with git. The git log is the most durable artifact because it is version-controlled by default. The git log documentation describes options for limiting by date, author, and message pattern. That is enough to reconstruct a change timeline even if no postmortem exists. The incident channel is the next most durable, depending on the retention policy. The postmortem repository is the least durable if it is not maintained, because a postmortem that is not reviewed is, in the words of the Google SRE book, a postmortem that might as well never have existed.

What if the CODEOWNERS file does not exist?

Then the ownership map is implicit. GitHub’s documentation states that CODEOWNERS files must be placed in .github/, the root, or docs/, and that GitHub searches those locations in that order and uses the first one it finds. If no file exists, ownership is defined by commit history and team conventions. That is a weaker artifact, but it is still an artifact. The commit history shows who touched what. The team conventions show who was expected to. The gap between the two is the ownership ambiguity that contributes to incident misattribution.

How often should the forensic pass run?

The pass is cheap. It is a query against artifacts that already exist. Run it quarterly, aligned with the review calendar’s own reporting cycle. The output is a gap report that either confirms the calendar or corrects it. Either outcome is better than assuming the calendar is the record.

The Promotion Packet Whose Impact Claims Have No Artifact Trail

Incident: A senior engineer’s promotion packet claims “led the migration that cut p99 latency by 40%” and “drove adoption of the new service mesh across three teams.” The packet is well-written. The committee is split. Nobody can find the migration RFC, the latency dashboard, or the adoption tracker. The packet is the only artifact.

Status: Open. Severity: Medium. Impact: The committee is now evaluating writing quality, not engineering impact. The engineer may be promoted or denied for reasons unrelated to their work. Both outcomes are wrong.

Symptom

The packet reads like a postmortem with the timeline removed. Claims are stated as facts. Numbers appear without units or baselines. Verbs are transitive but the objects are vague: “drove,” “led,” “owned,” “partnered with.” There is no artifact trail — no RFC link, no PR series, no dashboard snapshot, no incident review, no design doc with the engineer’s name in the revision history.

This is not a lying problem. It is a missing-artifact problem. The engineer did work. The work left traces. The traces are not in the packet. The committee is asked to reconstruct impact from prose.

Timeline

Promotion packets are usually assembled under deadline. The manager asks for a draft. The engineer writes it from memory, often late at night, often after a sprint. The manager edits for tone. The committee reads it cold, without the engineer in the room. The packet becomes the primary source.

If the packet cites artifacts, the committee can verify. If it does not, the committee either trusts the prose or discounts it. Neither is a system for evaluating engineering work. It is a system for evaluating packet-writing.

Contributing factors

1. The artifact trail is not part of the promotion process

Most engineering organizations have a career ladder and a promotion committee. Few have a requirement that impact claims link to artifacts. The ladder describes levels. The packet describes the engineer. The artifacts sit in git, CI, dashboards, and chat, unlinked.

This is a process gap, not a personal failing. The engineer is asked to prove impact in a format that does not require proof.

2. Impact is measured in outcomes, but artifacts record activity

Git history records commits. CI logs record builds. Incident channels record pages. None of these directly record “cut p99 latency by 40%.” That number lives in a dashboard, a query, or a before/after measurement. If the measurement was never saved, the claim is unverifiable.

This is the core mismatch: promotion packets claim outcomes, but the operational evidence teams generate records activity. The bridge between them — a saved measurement, a linked dashboard, a written before/after — is usually missing.

3. The committee has no shared standard for evidence

One committee member wants a design doc. Another wants a dashboard. Another wants peer feedback. Without a defined evidence standard, the committee defaults to prose quality and familiarity. This is how promotion becomes a writing contest with a social graph.

4. Retry storm: the same claims get re-litigated

When a packet lacks artifacts, the committee revisits the same claims in every calibration session. The claims do not resolve because there is nothing to resolve them against. This is a retry storm: the same request, the same failure, repeated until someone gives up or the deadline passes.

5. Normalization of deviance: everyone knows packets are unverifiable

The organization has learned that packets are mostly prose. The committee has learned to read between the lines. The manager has learned to write confidently. The engineer has learned to claim broadly. This is normalization of deviance: a known-bad practice that persists because it has not yet caused a visible failure. The visible failure is a bad promotion decision. It happens. It is attributed to the individual, not the process.

What the artifacts actually show

Pull the artifacts the team already generates. They tell a different story than the packet.

  • CODEOWNERS: Who is actually required to review changes in the affected area? If the engineer is not an owner, “led the migration” needs a different artifact.
  • Git history: Commit volume is not impact. But a series of commits across a migration window, with review approvals from other teams, is a trace. So is the absence of commits during the claimed period.
  • CI retry logs: If the migration introduced flaky tests that were retried for weeks, the packet’s “cut latency” claim may be true while the operational cost is omitted.
  • RFC threads: Who wrote the RFC? Who approved it? Who raised the blocking concern? The thread records the decision and the dissent.
  • Incident channels: Did the migration cause an incident? Was the engineer on call? Did they write the postmortem? The channel records the response, not the narrative.
  • On-call swap logs: If the engineer swapped out of on-call during the migration, that is a fact. It may be justified. It is still a fact.
  • Exit interview notes: If the migration drove someone out, the notes may say so. This is uncomfortable and relevant.
  • Career ladder docs: What does the ladder actually require at the next level? If it requires “impact across teams,” the packet needs cross-team artifacts, not a list of meetings.
  • PIP timelines: If a teammate was on a PIP during the migration, the packet’s “led the team” claim needs context.
  • Onboarding docs: If the migration required new onboarding steps, the doc is an artifact. If it did not, the migration may not have changed the system’s surface area.

None of these artifacts prove impact by themselves. Together, they form a trail. The trail either supports the packet’s claims, contradicts them, or is silent. Silence is a finding.

Why this gets misread as a personal failing

When the committee cannot verify a claim, it does one of two things. It trusts the engineer, or it discounts the engineer. Both are personal judgments. Neither is a system judgment.

The engineer who writes well and has a good reputation gets trusted. The engineer who writes plainly and has less visibility gets discounted. The difference is not impact. The difference is artifact trail and narrative skill.

This is the system failure: the promotion process rewards packet-writing and reputation, then attributes the outcome to engineering ability. The engineer who is denied is told to “build more visibility.” The engineer who is promoted is told they “demonstrated impact.” Neither feedback addresses the missing artifact trail.

Action items

For the committee: require artifact links for every impact claim

A claim without an artifact is a hypothesis. The committee can still consider it, but it should be labeled as unverified. This is not a punishment. It is a calibration. The committee’s job is to evaluate evidence, not prose.

Define the acceptable artifact types. A dashboard link with a saved query is an artifact. A PR series with review approvals is an artifact. A design doc with revision history is an artifact. A Slack thread is weaker but still an artifact. A verbal claim is not.

For managers: build the artifact trail before the packet deadline

The packet is a summary, not a discovery process. If the manager is writing the packet from scratch at deadline, the artifacts are already lost. Keep a running document per engineer: links to RFCs, dashboards, incident reviews, and peer feedback. This is not surveillance. It is the same evidence the committee needs.

For engineers: save the measurement, not just the outcome

When you run an experiment, save the before and after. When you ship a migration, link the dashboard. When you write an RFC, keep the thread. When you respond to an incident, write the postmortem. These are the artifacts that make impact legible.

This is not self-promotion. It is record-keeping. The organization already generates the evidence. The engineer’s job is to not lose it.

For the organization: define the evidence standard once

Publish the artifact types the committee accepts. Publish the format for linking them. Publish the rule that unverified claims are labeled. This removes the ambiguity that turns promotion into a writing contest.

HM Treasury’s Magenta Book provides a useful external reference for evaluation design: it emphasizes that evaluation should be built into an intervention’s design and delivery from the earliest stages, and that results need to be interpreted and presented with care. The same principle applies here. If impact evaluation is bolted on at packet time, the evidence is weak. If it is built into the work, the evidence is already there.

One reversible experiment

Pick one upcoming promotion packet. Before the committee reads it, ask the engineer and manager to add artifact links to every impact claim. If a claim has no artifact, mark it “unverified.” Run the committee session as usual. Compare the discussion to the previous cycle.

Rollback plan: If the artifact requirement slows the process without improving decisions, revert to the previous format for the next cycle. The experiment is reversible because it changes the packet format, not the promotion criteria.

What to measure: Did the committee spend less time debating unverifiable claims? Did the packet’s claims survive contact with the artifacts? Did any claim get withdrawn or narrowed? These are signals, not proof. But they are more useful than another cycle of prose review.

FAQ

Doesn’t requiring artifacts disadvantage engineers who work on invisible infrastructure?

It disadvantages engineers whose work leaves no artifacts. Infrastructure work leaves artifacts: dashboards, runbooks, incident reviews, migration RFCs, on-call logs. The problem is not invisibility. The problem is that the artifacts are not linked to the packet. If the work truly leaves no trace, that is a finding about the work, not the engineer.

Isn’t this just bureaucracy?

It is record-keeping. The organization already generates the artifacts. The packet currently ignores them. Linking them is less work than re-litigating the same claims in every calibration session.

What if the engineer did the work but the artifacts are owned by someone else?

Then the packet should say so and link the artifact. Ownership of the artifact is not the same as contribution to the outcome. The committee can evaluate contribution from the artifact trail: review approvals, RFC comments, incident response, commit history. The artifact does not need to be authored by the engineer. It needs to be linked.

How does this relate to the career ladder?

The ladder defines the expected level of impact. The artifacts define whether the impact occurred. If the ladder says “impact across teams,” the packet needs cross-team artifacts. If the ladder says “technical leadership,” the packet needs design docs, RFCs, or incident reviews. The ladder is the standard. The artifacts are the evidence.

What about qualitative impact, like mentoring or culture?

Qualitative impact still leaves artifacts: onboarding docs, peer feedback, exit interview notes, promotion packets of people the engineer mentored. These are weaker than dashboards but stronger than prose. The committee can weigh them accordingly.

Closing note

The packet is not the problem. The missing artifact trail is the problem. The packet is a symptom. The system failure is that promotion decisions are made on prose when the organization already generates evidence. Fix the trail, and the packet becomes a summary instead of a mystery.

The Architecture Decision Record That Records the Decision But Not the Duress

The Architecture Decision Record That Records the Decision But Not the Duress

The ADR is supposed to be institutional memory. What actually gets merged is the clean version. Six months later, a new engineer reads a confident decision and reverse-engineers a rationale that never existed.

SYMPTOM // REVERSE-ENGINEERED RATIONALE

A team I’ll call the Marlow group had a service that handled payment webhooks. Single-tenant Postgres instance, no read replicas, no connection pooling, and a max connections setting that the original engineer had guessed at. When the service started timing out under load, the team wrote an ADR proposing a migration to a connection pooler in front of a multi-tenant cluster. The ADR was clean. Context section, decision section, consequences section. Alternatives listed. Approved. Also fiction.

The migration had been proposed because the original engineer — the one who guessed at the max connections setting — had left three months earlier, and the person who inherited the service didn’t know how to tune Postgres. The ADR framed this as a scaling decision. The actual context was a knowledge gap and a deadline. The dissenting voice in the RFC thread — a backend engineer who had argued for trying pgbouncer on the existing instance first — was overruled in a meeting that wasn’t referenced in the document. Their comments were in the thread. But the reply that closed the discussion — “Let’s go with the migration, we can revisit pooling later” — was timestamped 11 PM on a Friday. The engineer who objected went on vacation the next morning. By the time they came back, the ADR was merged.

Six months later, a new hire read the ADR and concluded that the team had deliberately chosen a multi-tenant architecture for long-term scalability. They built new services on the same pattern. When one of those services hit the same connection exhaustion problem, they assumed it was a different bug — because the ADR said the pattern was chosen for scaling, not because of a knowledge gap. The rationale they inherited was a story the team told after the fact. It was not the reasoning that produced the decision.

I’ve watched this pattern break at least four teams. Not the Postgres part — the documentation part. The ADR, the RFC thread, the technical strategy document: these are supposed to be the institutional memory of why we chose what we chose. But the artifact that gets merged or approved is almost always the clean version. The dissenting comments, the “I disagree but won’t block” caveats, the concerns raised in a side channel that never made it into the thread — that’s the actual decision context. And it’s the part nobody writes down.

§ 1 — WHAT THE ARTIFACT HIDES

The function of an ADR is to preserve decision context across time and team turnover. The failure mode is that the artifact preserves the outcome but strips the duress. Duress is the pressure under which the decision was actually made: the deadline, the missing expertise, the political constraint, the person who disagreed but was too tired to fight. Strip the duress and you leave behind a decision that looks rational and deliberate. The next reader assumes it was.

Google’s SRE book dedicates an entire chapter to postmortem culture and the principle that documenting what actually happened — including the messy, uncomfortable, politically charged reality — is the only way an organization learns from failure rather than repeating it with better confidence. The Google SRE book’s postmortem culture chapter is the closest established parallel to this problem: the gap between the clean summary and the actual timeline is where institutional knowledge dies. The same principle applies to ADRs. A postmortem that records the outage but not the contributing factors teaches nothing. An ADR that records the decision but not the duress teaches the wrong thing.

The specific signals of suppressed dissent are in the artifact itself. You just have to know what to look for.

§ 2 — THE FORENSIC READ

Pull the last five merged ADRs or approved RFCs from your team’s repository. For each one, inspect these four signals:

1. Comment threading depth versus final document depth. Count the nested reply levels in the discussion thread. Then check whether the concerns raised in the deepest branches appear in the final document. If the thread has four levels of nested replies and the ADR’s Alternatives Considered section has two sentences, you have a gap. The deepest branches are where the real disagreement lives — that’s where someone stops arguing with the proposal and starts arguing with the person who made it. If that content doesn’t surface in the ADR, the document is hiding the decision’s actual shape.

In the Marlow group’s ADR, the thread had a branch that went five levels deep on whether pgbouncer in transaction mode would break prepared statements. The final ADR’s alternatives section said: “Connection pooling was considered but deemed insufficient for projected scale.” No mention of prepared statements. No mention that the objection was technical, not strategic. The sentence reads as if pooling was evaluated and dismissed on its merits. The thread shows it was dismissed because the person who understood the tradeoff went on vacation.

2. Edit timestamps versus comment timestamps. Most RFC tools and wiki platforms expose edit history. Compare the document’s last edit time to the last substantive comment. If the document was edited after the last comment without that comment’s content being incorporated, you’re looking at a decision that was finalized by editing, not by discussion. The edit is where the author quietly resolved the disagreement by removing it from the record.

This is the most common suppression pattern I see. Someone raises a concern. The author agrees to “address it” in the document. The edit that follows doesn’t address it — it reframes it. The concern about prepared statements becomes “transaction mode limitations.” The concern about timeline pressure becomes “phased rollout recommended.” The original objection is gone. In its place is a passive-voice recommendation that nobody is accountable for.

3. The gap between Approved and the last substantive objection. Measure the time between the final substantive technical objection and the approval. If it’s under 24 hours, and especially if it’s under 4 hours, the approval happened before the objection could be resolved. This doesn’t mean the decision was wrong — it means the process didn’t include the objection. The objection existed, was recorded, and was overridden by time pressure, not by argument.

In a healthy process, the gap between the last objection and the approval reflects the time it took to address the objection. In a broken process, the gap reflects the time it took for the objector to give up. You can’t tell the difference from the document alone — but if the document doesn’t mention the objection at all, you can infer which one happened.

4. The conspicuous absence of certain voices. Cross-reference the list of people in the meeting where the decision was finalized against the list of people who commented in the RFC thread. If someone was in the meeting but not in the thread, their position on the decision is unrecorded. If that person is the most senior engineer on the team, or the person who owns the system the decision affects, their silence in the artifact is a signal. They may have agreed. They may have been overruled. You can’t tell — and that’s the problem.

The absence is the hardest signal to detect because it requires knowing who should have been in the thread. But it’s the most reliable indicator of duress. When the people who have the most context on a decision don’t appear in the written record, the written record is incomplete. The decision was made somewhere else. The document is a backfill.

§ 3 — THE REVERSE-ENGINEERED RATIONALE

Here’s what happens six months later. A new engineer — call them the inheritor — reads the ADR. They see a clean decision with a context section, alternatives, and consequences. The document reads as if the team evaluated the options and chose deliberately. The inheritor doesn’t have access to the thread. Or the thread has been archived. Or the tool has changed. Or the comments are in a Slack channel that was deleted when the workspace was reorganized. The inheritor does what engineers do: they reverse-engineer the rationale from the decision.

If the ADR says “We chose a multi-tenant architecture for long-term scalability,” the inheritor assumes the team had evidence of scaling problems. They build on that assumption. They extend the pattern. They cite the ADR in their own RFC as precedent. The original decision was made under deadline pressure by someone who didn’t know how to tune a database. The inheritor’s new decision is made under no pressure by someone who thinks they’re following a proven pattern. The fiction has become infrastructure.

This is how teams accumulate architectural decisions that nobody can explain. Not because the original decision was bad — it might have been fine under the circumstances. But because the circumstances weren’t recorded, the decision can’t be evaluated, only inherited. Every inherited decision narrows the space of what the team considers possible. And none of them can be questioned because none of them have a recorded rationale that can be examined.

The result is a codebase that feels inevitable. Why is everything multi-tenant? Because the ADR said so. Why did the ADR say so? Because that’s what we decided. The circularity is invisible because the duress that broke the circle was stripped from the record.

§ 4 — THE SAME FAILURE IN NARRATIVE PLANNING

This isn’t an engineering-only problem. Any team that maintains long-form written decision records faces the same core issue: the artifact that survives is the one that’s been cleaned up, not the one that’s true.

Consider a writing team that maintains editorial outlines — plot decisions, character arc rationales, structural choices documented for collaborators and for future reference. The same suppression pattern appears. The outline says “We chose a non-linear timeline to create thematic resonance between the two storylines.” The actual decision was made because the first draft had a pacing problem in act two and the editor suggested restructuring rather than rewriting. The outline doesn’t say that. Six months later, a collaborator reads the outline, assumes the non-linear structure was a deliberate artistic choice from the start, and builds subsequent plot decisions on top of a rationale that was never artistically intended — it was a fix that got promoted to a principle.

The Authors Guild’s guidance on writing practices emphasizes preserving the original voice, thinking, and creative reasoning that shape a work — the reasoning behind a decision is part of the work, not just the output. When editorial decision records strip that reasoning, they strip the same thing a duress-free ADR strips: the context that makes the decision legible to the next person who has to build on it.

The bridge is narrow and honest: teams that use AI novel writing software that preserves the decision trail alongside the draft face the same temptation to clean up the artifact. The tool can record the reasoning, the alternatives considered, and the disagreements — but only if the team treats the decision record as part of the work, not as paperwork to be filed after the work is done. The failure mode is identical whether the artifact is an ADR in a git repo or a narrative outline in a writing tool: the cleaned version becomes the official version, and the real context is lost.

§ 5 — ONE REVERSIBLE EXPERIMENT

For the next ADR your team writes, add a section called Decision Context — distinct from Context, which describes the technical situation. Decision Context records the conditions under which the decision was made. It answers four questions:

What deadline or pressure was active when this decision was finalized? Not the project deadline — the decision deadline. If the decision had to be made by Friday because someone was going on vacation, that’s the context. If it had to be made before a funding round, that’s the context. If there was no pressure and the team had unlimited time, write that — it’s the most important signal of all.

Who disagreed, and what was their objection? Name the objection, not the person. “An objection was raised that transaction-mode pooling would break prepared statements used by the webhook consumer. The objection was not resolved before approval.” That sentence tells the next reader everything they need to know: the decision was made with an unresolved technical concern, and the concern is specific enough to investigate.

What was the strongest argument against this decision? Not the alternatives that were considered — the strongest case for not doing it at all. If the ADR can’t articulate the best argument against itself, the ADR wasn’t written by someone who understood the tradeoff.

What would make us revisit this decision? The conditions under which the decision should be re-evaluated. Not “if requirements change” — that’s meaningless. “If the webhook service exceeds 500 connections per second, the pooling decision should be revisited because the current rationale was based on projected load of 200 cps.” That gives the inheritor a concrete test, not a vague disclaimer.

The experiment: add the Decision Context section to one ADR. Don’t mandate it for all ADRs. Don’t change the template. Don’t announce a process change. Write one ADR with the section and see what happens when the next engineer reads it. If they ask better questions about the decision, the section earned its place. If nobody reads it, you have a different problem — your ADRs aren’t being read at all, and the duress issue is downstream of a documentation culture that doesn’t exist yet.

Rollback: if the section creates friction — if people spend more time arguing about what counts as duress than making decisions — remove it. The section is a diagnostic, not a process. Its value is in what it reveals about the decision, not in its compliance as a template field. If it becomes a checkbox, it’s failed. A good Decision Context section reads like a person writing honestly about what happened. A bad one reads like a form being filled out. The difference is obvious.

§ 6 — THE ARTIFACT IS THE EVIDENCE

The ADR is not a record of what the team decided. It’s a record of what the team was willing to write down about what they decided. Those are different things. The gap between them is where the next team’s confusion lives.

When you read an ADR, read it forensically. Look for the threading depth that didn’t make it into the summary. Look for the edit that came after the last comment. Look for the approval that came too fast. Look for the voices that aren’t there. The document is telling you what happened — but it’s telling you by what it omits, not just by what it says.

And when you write one, write the duress. Write the deadline. Write the objection that wasn’t resolved. Write the argument against your own decision. The clean version is the one nobody learns from. The messy version is the one that saves the next engineer from building on a rationale that never existed.

The ADR is not a record of what the team decided. It’s a record of what the team was willing to write down about what they decided.

How to Build an Engineering Culture That Survives Growth: A Postmortem in Advance

Every team I’ve ever audited tells me some version of the same story: everything worked fine until we grew. And that’s accurate, as far as it goes. It just isn’t a defense. A culture that only functions at one headcount isn’t culture; it’s a side effect of a small broadcast domain. Nobody lost their values when the team doubled. The hallway stopped scaling, and nothing got written down to replace it.

So treat this the way you’d treat any other scaling problem. Stop asking people how the culture feels and go read the artifacts the team already produces: git history, CI retry logs, RFC threads, incident channels, exit interview notes, career ladder docs. The failure modes have names, and you can find most of them in an afternoon. That’s the whole method on this site — pull the evidence, name the mechanism, run a reversible experiment. No trust falls required.

What an engineering culture that survives growth actually means

Definition first, because “culture” is a word that absorbs whatever meaning the speaker needs at the time. An engineering culture is the set of defaults a team follows when nobody is watching: how decisions get made, how disagreement gets resolved, how bad news travels. A culture survives growth when those defaults still work after the people who created them are no longer in every room. That’s the entire test. Not whether the offsite was fun. Whether a new hire in a satellite office, six time zones away, makes the same call the founders would have made, for reasons the founders would recognize.

Most culture programs fail this test because they treat culture as a communications problem: values documents, culture decks, all-hands talks. Those are artifacts too, and I read them, but they record intent. The git history records behavior. When the two disagree, believe the git history. They usually disagree.

Symptom: “the culture was fine until we doubled headcount”

The complaint arrives as a people problem. The evidence usually says otherwise. Here is the standard deterioration timeline, with the mechanism doing the damage at each stage.

Stage 1: Osmosis (5 to 15 engineers)

Culture travels by proximity. Every decision happens in a room where everyone is present, or in the one channel everyone reads. Corrections take seconds. This feels like a strong culture, and it is one — with a single point of failure. The failure mode is a hiring plan.

Stage 2: Folklore (15 to 40)

Decisions start getting re-litigated. Not because anyone is being difficult — because the original context lives in one person’s head, and that person is on vacation, or on another team now. A decision that has to be won twice is a retry storm with human actors: repetitive, expensive, and it ties up your most senior people every quarter.

Stage 3: Deadlock (40 to 100)

Approval chains close into loops. Team A needs B’s sign-off, B is waiting on C’s security review, C is blocked on A’s architecture decision. Nobody in that loop is doing bad work. The wait-for graph simply has a cycle in it, and the org chart is the scheduler now. Conway said it first: organizations build systems that mirror their communication paths, and the law he described cuts both ways. Fix the paths or inherit the deadlock.

Stage 4: Normalization of deviance (100 and beyond)

The flaky test gets retried until it goes green. The alert that fires 400 times a day gets muted. The postmortem gets skipped because it’s a busy week, then a busy quarter. Diane Vaughan coined normalization of deviance studying the Challenger launch decision: each small deviation gets absorbed as normal, right up until the day it isn’t. Software teams run the same pattern on a shorter fuse and call it a busy quarter.

Engineering team in a discussion around a conference table

Contributing factors, read off the evidence

A postmortem doesn’t assign blame. It lists contributing factors. These are the ones I find most often, plus the artifact that convicts each one.

  • Review concentration. git shortlog -sn shows one name attached to most of the merges. That’s a bus factor of one wearing a mentorship costume.
  • CI retry storms. The pipeline passes on attempt three and everyone has quietly agreed not to notice. The team has stopped trusting its own tests, and honestly, it is right not to.
  • Decision debt. RFC threads that end in “let’s revisit this in Q3” instead of a decision with an owner and a date.
  • Blame-shaped incident language. “Who touched this” instead of “what did we not know.” Blameless postmortems aren’t a politeness ritual — they are how you keep the people holding the information willing to talk. Google’s SRE workbook makes the case plainly in its chapter on postmortem culture.
  • A career ladder made of fog. The document says “impact” forty times and defines it zero times. Promotion becomes a vibes call, and the vibes are set by whoever is loudest in the room.
  • PIP spikes after reorgs. Performance improvement plans cluster three months after a reorganization. Eight people do not independently get worse at their jobs in the same quarter. Read it as the org chart failing, not the individuals.

The audit: one afternoon, six pulls

You don’t need a consultant for this. You need read access and an afternoon.

  1. Git history. Run git shortlog -sn --since='6 months ago' across your repositories. Who merges, who reviews, and how concentrated is the load? There’s a longer walkthrough in my guide to reading git history for team dysfunction.
  2. CI retry logs. Count pipeline runs per commit. Retry-until-green is the most honest number in your build system. I keep a fuller walkthrough on reading CI retry storms.
  3. RFC threads. Find the decisions that were made and then made again. Every reopened decision is evidence that the original had no owner and no expiry date.
  4. Incident channels. Search the first ten messages of your last ten incidents for the word “who.” Count the hits. That’s your blame index, and you can watch it move month over month.
  5. Exit interview notes. “Compensation” is what people say when the real reason would burn a bridge. Look for what the leavers had in common: same team, same manager, same quarter. Patterns, not people. There’s a fuller method in my notes on exit interview forensics.
  6. PIP timelines. Plot them against org changes. Correlation isn’t causation, but a cluster is a confession.
Two engineers reviewing delivery metrics on a laptop

Action items: six reversible experiments

Each experiment below has a success signal and a rollback condition. That’s the part most culture programs skip, which is why most culture programs fail: all commit, no rollback.

  1. Put expiry dates on decisions. Every significant decision gets a short record: what was decided, why, who owns it, and a revisit date. The revisit date is the trick. It ends the quarterly re-litigation, because the decision can be reopened — on schedule, by anyone, with the original context attached. Success signal: fewer reopened RFCs. Rollback: drop the practice after a quarter and see whether the retry storm comes back. It will.
  2. Break approval deadlocks with a default. Take the three most-looped approvals in the org and add one rule: five working days with no substantive objection, and the owner’s call stands. A deadlock is a wait-for graph missing a timeout. Add the timeout.
  3. Quarantine flaky tests instead of retrying them. A test that passes on the third attempt isn’t passing; it’s gambling. Quarantine it, make the quarantine list public, fix the test, and watch the list shrink. If the list grows instead, you’ve learned something about the team’s relationship with bad news.
  4. Cap review load, then check the logs. Set a soft weekly cap on reviews per person. A month later, run git shortlog again. If the concentration didn’t move, the cap was theater, and now you know that too.
  5. Rewrite the career ladder in observable behavior. Replace every “impact” and “excellence” with something you could show a stranger: designs reviewed, incidents led, decisions documented and not reopened. Ambiguity in a ladder isn’t flexibility. It’s a buffer for bias.
  6. Change the exit interview question. Stop asking why they’re leaving. Ask: what did this team keep misreading about how work actually gets done? The answers get less polite and far more useful.
Engineer working at a monitor with code on screen

Misdiagnoses: what the evidence gets called instead

Every mechanism above has a personnel-shaped alias. Review concentration becomes “our senior engineer doesn’t delegate.” Decision debt becomes “we need better alignment.” Deadlock becomes “engineering is slow.” A blame index becomes “one or two bad apples.” The misdiagnosis is expensive because it triggers the wrong treatment: coaching for a structural failure, a reorg for a documentation failure, more hiring for a throughput failure. You would not rename a deadlock “insufficient ambition” and expect the process to unblock. Don’t do the equivalent to people.

What a culture that survives growth actually looks like

Boring, mostly. Decisions that stay decided until their revisit date. A CI pipeline the team trusts enough to block a release on. Incident channels that read like diagnosis instead of deposition. A career ladder a stranger could apply to real work. None of it photographs well for the about page. All of it compounds.

The test I use is simple. Can a new hire answer “how do we do this here?” by reading, without asking one specific person? If yes, the culture lives in the artifacts, and it will survive the next doubling. If no, it lives in heads, and heads are portable.

FAQ: engineering culture that survives growth

Can you measure engineering culture without a survey?

Yes, and the measurements are better, because artifacts don’t perform. Six numbers cover most of it: review concentration from git history, retry-until-green rate from CI, reopened-decision count from RFC threads, blame index from incident channels, PIP-to-reorg correlation, and the recurring pattern in exit notes. A survey tells you what people think is safe to say. The logs tell you what happened.

What’s the first artifact to pull if I only have an afternoon?

CI retry logs, then git shortlog. A team that retries its tests until they pass has already told you the feedback loop is broken and the deviance is normalized. Those two pulls take twenty minutes and usually explain half the symptoms people keep describing as interpersonal.

When should a founder stop reviewing every pull request?

Earlier than feels right. A founder review bottleneck looks like quality control right up until it becomes a hiring ceiling and a bus factor of one. If your name is on most of the merges, you aren’t raising the bar. You are the queue.

Does hiring for “culture fit” work?

It selects for sameness, which reads as harmony and behaves like monoculture. Fit-based hiring quietly builds a team that agrees with itself, which is great for lunch and terrible for incident reviews. Hire for the artifacts you want: people who write decisions down, review generously, and refuse to retry broken things until they look whole.

How long does fixing this take?

The audit is an afternoon. The experiments need about a quarter before the signals are readable. The deterioration you’re correcting took years, so a sprint won’t do it — but the first artifact pull is free, and it usually changes the very next meeting you’re in.

One last thing. Culture failure gets narrated as personal failure: the difficult engineer, the underperformer, the manager who couldn’t scale. The artifacts usually tell a different story, one where the same three mechanisms — deadlock, retry storms, normalized deviance — chew through perfectly competent people. Pull the evidence before you write the performance review. The system is confessing. Read the logs.

Why Most Architecture Documents Are Fiction Worth Less Than the Code

Most architecture documents are fiction. Not malicious fiction â nobody is lying to you on purpose â but fiction all the same: a confident narrative about a system that stopped matching reality somewhere between the approval meeting and the next reorg. The code, meanwhile, tells the truth every single day. It may be ugly, badly organized, commented in a dead language, but it is the only artifact in your company that cannot lie about what the system does. Which raises an uncomfortable question about why we keep producing forty-page PDFs describing it.

I have read a lot of these documents. In postmortems, in onboarding folders, in the quiet despair of a wiki nobody visits. The worst one I owned described a payments queue that was decommissioned before I was hired; two cohorts of new engineers learned about a service that never existed. The pattern is consistent enough to be boring: the document is a snapshot of intent, taken at the moment the author needed approval, and never updated again. Everything after that snapshot is a story the code writes without the author.

Engineers around a conference table during a design review, the meeting most architecture documents are written for and never updated after.
The last moment the document is guaranteed to be true.

The document has no write path

State the core mechanic bluntly: code has a write path, and documents don’t. Code gets merged several times a day. Every pull request, every hotfix, every 2 a.m. hack rewrites the source of truth. The document’s write path ran exactly once, on approval day, and was then decommissioned. What you are left holding is a cache entry with no TTL, no expiry check, and no owner. It serves stale data with total confidence until somebody trusts it at the worst possible moment.

This is a race condition between two artifacts that are supposed to agree. One commits a hundred times a week. The other last wrote to disk in 2022. Guess which one wins.

Nobody notices, because nothing depends on the document. Dead code at least gets flagged by a linter or deleted in a cleanup sprint. Dead documents get bookmarked. They sit in the onboarding folder with the institutional equivalent of a verified badge: a senior engineer linked to it once, so it must be true.

What the document actually records

Read a stale architecture document carefully and you notice it was never really about the system. It is about the meeting. It records what the author needed to say to get the design through review: the tradeoffs that convinced the room, the boxes that looked tidy on a slide, the org chart as it existed that quarter. A transcript of a social ritual, typeset in a diagramming tool.

This is why reorgs kill these documents so reliably. The doc captures Conway’s law as it stood at approval time. Two reorgs later, the platform team it references has been split, merged, renamed, and split again. The document is now alternate-history fiction about an organization that no longer exists, while the actual system has quietly reshaped itself around the new org, with nobody writing any of it down. It has become a legacy system without the one decency legacy systems offer, which is that they still run.

Four operational signals your docs are fiction

You don’t have to audit the wiki to diagnose this. Like most organizational failures, stale documentation shows up in operational exhaust â the signals your team produces anyway, without meaning to. Four of them, roughly in the order they surface.

New hires route their questions around the documentation

Watch where a new engineer’s questions actually go. If the onboarding doc were load-bearing, the questions would thin out after week two. They don’t. They route to the same two senior engineers, every cohort, forever: âwho owns this service,â âwhy is there a second checkout path,â âis that diagram current.â The last one is the tell. When people ask whether a document is true before they are willing to read it, the document has already failed. Those two seniors are doing the documentation’s job by hand, in interrupt-driven mode. If you want the longer version: onboarding questions are a systems signal, not a people problem.

During incidents, nobody opens the doc

Incident response is documentation’s honesty test. At 2 a.m. with a customer-facing outage, people reach for whatever has the shortest path to the truth: dashboards, logs, git blame, the person who wrote the code. If the architecture overview or the runbook gets opened at all, watch what happens next. Do responders act on it, or do they open it, grimace, and keep scrolling? Opening a stale runbook mid-incident is a blocking call that returns the wrong answer and charges you for the latency. It burns the one resource you cannot recover mid-incident: attention.

An engineer reading code on a laptop late at night, taking the shortest path to truth during an incident.
The shortest path to truth at 2 a.m. It is not the wiki.

Doc edit dates lag code churn by years

This one you can measure in an afternoon. Take your five most-linked documents and compare the last substantive edit date against the commit rate of the services they describe. A document last touched in March, describing a service that has taken four hundred commits since, is fiction with a header image. The gap between those two numbers is your documentation debt. Unlike technical debt, nobody has built a dashboard for it, so it compounds quietly.

A recurring meeting exists to explain the architecture

The monthly architecture walkthrough is not a knowledge-sharing initiative. It is a compensating control for a dead write path. The system’s description lives in one person’s head, and the meeting is the cron job that re-renders it for an audience. It works, mostly, the way manual retry loops work â until the person with the architecture in their head is on vacation during an incident and everyone discovers the wiki page was a placeholder. Recurring meetings are compensating controls more often than anyone admits; this is one of the textbook cases.

Root cause: the document’s only consumer was the approval

Run this like a postmortem and the root cause is almost embarrassingly simple. Architecture documents are approval artifacts. Their entire consumer base is one meeting that happens once. After approval, the document has no readers with the power to change it, no owner accountable for it, and no trigger that fires when the system it describes gets rewritten. A component with no consumers, no owner, and no alerts doesn’t stay accurate out of spite. It rots, the way anything unowned rots.

Contributing factors, in the usual style: updating the doc helps a future teammate nobody has met, while skipping it costs nothing today. The author has since moved to another team, where they are currently writing a new document that will also be true for about six weeks. And nothing in any feedback loop â the review, the retro, the ops review â notices the gap, because the gap has no metric. This is not a character flaw. Teams skip documentation maintenance for the same reason they skip any maintenance: unowned things rot, and nobody gets paged when they do.

What a document is actually worth

The what belongs to the code. The why is the only thing worth writing down. Code answers what the system does with total fidelity and zero context. It can’t tell you why the alternative was rejected, which invariants are load-bearing and which behaviors are incidental scar tissue from a 2019 incident, or which parts of the design are deliberate and which are leftovers nobody dared to delete. That information exists in exactly three places: the heads of people who were there, old pull request threads, and nowhere. Documents that capture the why survive contact with change. Documents that duplicate the what get outcompeted by git blame and die quietly.

So the fix is not âupdate the wiki more,â whatever the consultant deck says. The fix is to stop writing documents that describe the system and start writing documents that record decisions.

Architecture decision records are the boring, durable version of this. One page per decision: context, options considered, what we chose, what we expect to regret. Michael Nygard’s short 2011 note on documenting architecture decisions is still the best thing written on the subject, and it is shorter than most design review agendas. Each record is immutable, so it cannot drift â it can only be superseded by a newer record, which leaves a visible trail. You get a git history for reasoning instead of a PDF that gets quietly rewritten until it means nothing.

For the parts that genuinely need describing â system context, trust boundaries â keep it small. Simon Brown’s C4 model is the sane version: one context diagram, maybe one container diagram, then stop. Anything more detailed is a re-implementation of the codebase in a diagramming tool, and the codebase wins that race every time, because it has a write path and the drawing doesn’t. Google’s public write-up of its design doc practice lands the same point from the other end: the document exists to align people and record reasoning, not to mirror the code.

Two teammates reviewing a short decision record together at a laptop.
A decision being argued over in person. That argument is the part worth writing down.

If you insist on keeping documents, treat them like systems

Short list, deliberately. Everything on it is a mechanism, not an exhortation.

  • Every document gets an owner and a last-reviewed date. In the header, not in metadata nobody renders. A page without those two fields is an unowned service, and you already know what happens to those.
  • Somebody owns the kill switch. When a pull request moves a trust boundary or splits a service, the review checklist asks which document it just made wrong. One question, asked at the moment the ground truth moves.
  • Supersede, don’t edit. Rewriting history inside a doc is how you get fiction with a fresh timestamp. Mark the old decision superseded and link the new one.
  • Review docs where you review ops. The quarterly review that already looks at deployment frequency and incident load can spend ten minutes on documentation freshness. Same lens, same meeting, no new ritual.
  • Delete more than you update. A dead document that admits it is dead is harmless. A dead document with confidence is the expensive one.

Frequently Asked Questions

Why do architecture documents become fiction so quickly?

Because their write path runs exactly once, at approval, while the code they describe commits daily. With no owner, no expiry trigger, and no review cadence, every document starts drifting the moment the first post-approval pull request lands. It isn’t carelessness. It is a system with no feedback loop.

What should an architecture document contain instead of system descriptions?

Decisions, rejected alternatives, invariants, and constraints â the why the code cannot express. Record what you chose, what you turned down, and what you expect to regret. Topology, sequence flows, and component inventories belong to the code, or to diagrams you can re-render from it. Anything maintained by hand that a repository can tell you is a second source of truth, and second sources of truth always diverge.

How can I tell if my team’s documentation is fiction?

Check the operational signals before reading a word: where new hires route their questions, whether incident responders open the runbook and act on it, the gap between each document’s last edit date and the commit rate of the services it describes, and whether a recurring meeting exists to explain the architecture the docs were supposed to cover. If two or more of those look bad, the docs are fiction regardless of how good the prose is.

Are architecture decision records worth the overhead?

Yes, mostly because the overhead is one page per decision and the alternative is expensive. The cost lands at the moment you are already thinking hard about the decision anyway. The payoff shows up years later, when somebody asks why the system looks like this and the answer is a document instead of an archaeology dig through closed pull requests and whoever is still around to remember.

The uncomfortable summary

Your architecture document was a true story once, on the afternoon it was approved. Everything since has been the code changing and the document declining to notice. Stop asking teams to maintain descriptions the code maintains better, and spend the writing effort where the code cannot go: decisions, rejections, reasons. That part earns its bytes. The rest was always worth less than the code, and pretending otherwise is how the wiki got this bad.

The Difference Between Mentoring and Gatekeeping Through Code Review

Code review is a control point. It’s where an organization decides whether knowledge moves through the system or gets queued behind a senior engineer’s preferences. The difference between mentoring and gatekeeping isn’t intent. It shows up in review latency, comment density, approval patterns, and the number of times a diff gets bounced for reasons that were never in the ticket. If your team treats code review as a quality gate, you have a process. If your team treats it as a teaching interface, you have a system. Most teams have neither. They have a blocking call wrapped in politeness.

This article is for senior ICs and engineering managers who suspect their team’s dysfunctions are system failures, not personal failings. We’ll look at code review as an operational signal: what it reveals about knowledge distribution, decision rights, and the actual cost of “just one more nit.” The goal isn’t to make code review nicer. The goal is to make it legible.

Code Review as a System, Not a Conversation

When a pull request sits for three days, that’s not a conversation. That’s a queueing delay. When a reviewer leaves forty comments on a two-hundred-line diff, that’s not thoroughness. That’s a batch process with no service-level objective. When the same two people approve every critical path change, that’s not expertise. That’s a single point of failure with a bus factor of one.

Operational signals from code review are easy to collect and almost never collected. Review latency, first-response time, comment-to-merge ratio, rework cycles, approval depth, and the distribution of reviews across the team. These numbers will tell you more about your team’s sociology than any retro. A team where reviews are fast, shallow, and widely distributed is a team that trusts its tests and its onboarding. A team where reviews are slow, deep, and concentrated is a team that has institutionalized distrust and called it quality.

Mentoring through code review means the reviewer’s primary output is a more capable author. Gatekeeping means the reviewer’s primary output is a merged diff that matches their mental model. Both can produce working code. Only one produces a team that can survive the reviewer’s vacation.

The Latency Signal: Blocking Calls and Starvation

In distributed systems, a blocking call is one that stops the caller until the callee returns. Code review is a blocking call in the development pipeline. The author can’t merge until the reviewer responds. If the reviewer is overloaded, the author starves. If the author is junior, the starvation is worse, because they have fewer alternative tasks and less political capital to escalate.

Review latency is the single most honest metric in engineering culture. It tells you whose time the organization values. A team that measures cycle time but not review latency is optimizing the wrong variable. A team that celebrates “fast merges” while two senior engineers hold a de facto veto is running a race condition: the system appears fast until the critical section is contended, and then everything stops.

Mentoring systems set expectations for review latency. They treat a stale review as an incident, not a personality trait. Gatekeeping systems treat latency as a feature: the longer the wait, the more the author learns to write code that won’t get comments. That’s not learning. That’s avoidance training.

Comment Density and the Nitpick Tax

Every comment on a code review has a cost. The author must read it, respond to it, and often change the code. The reviewer must re-review. The team must wait. A comment that catches a real bug is worth that cost. A comment that renames a variable to match the reviewer’s taste is a tax. A comment that asks a question already answered in the ticket is a tax with interest.

Mentoring reviewers separate blocking comments from non-blocking comments. They use prefixes like “nit:” or “optional:” and they mean it. They don’t leave twenty nits on a diff and then approve it, because that teaches the author that nits are noise. Gatekeeping reviewers leave twenty nits and request changes, because the nits aren’t about the code. They’re about the reviewer’s need to be the smartest person in the thread.

There’s a simple test. If you removed every comment that could be fixed by a linter, a formatter, or a style guide, how many comments remain? If the answer is close to zero, your code review process is a style enforcement mechanism, not a quality mechanism. You have automated nothing and called it rigor.

Approval Patterns and the Bus Factor

Look at the last fifty merged pull requests. Count how many were approved by each engineer. If the distribution is roughly uniform, you have a healthy review culture. If two engineers account for eighty percent of approvals, you have a gatekeeping system, even if those engineers are kind, patient, and well-intentioned.

Concentrated approval power is a legacy system. It works until the key person leaves, burns out, or becomes a bottleneck. Then the team discovers that nobody else knows how to review a database migration, because nobody else was ever allowed to. The knowledge was never transferred. It was rented.

Mentoring systems deliberately rotate reviewers. They pair a senior engineer with a mid-level engineer on the same review. They let a junior engineer approve a trivial change and then discuss the decision in a follow-up. They treat review authority as a skill to be distributed, not a privilege to be hoarded. Gatekeeping systems treat review authority as a credential. The credential is never explained, never tested, and never revoked.

Rework Cycles and the Hidden Curriculum

A rework cycle is when a pull request is sent back to the author for changes. One rework cycle is normal. Two is a conversation. Three or more is a failure of the review process, not the author. Either the requirements were unclear, the reviewer changed their mind, or the reviewer is using the diff as a drafting table for their own design preferences.

Rework cycles are where gatekeeping hides. A gatekeeper will request changes, then request more changes, then request a rebase, then request a test that was never in the acceptance criteria. Each cycle looks reasonable in isolation. The pattern is the signal. The author isn’t learning to write better code. They’re learning to predict the reviewer’s mood.

Mentoring systems cap rework cycles. They say, explicitly, “If this diff needs more than two rounds, we’ll schedule a synchronous conversation.” That conversation is where the actual knowledge transfer happens. The code review was never the right medium for a design debate. It’s a diff, not a whiteboard.

Documentation Freshness as a Review Artifact

Code review isn’t just about the code. It’s about the decisions that produced the code. A healthy review process leaves artifacts: updated runbooks, revised ADRs, clarified ticket descriptions, new tests that encode a previously implicit assumption. An unhealthy review process leaves only the diff and a trail of comments that will never be read again.

Mentoring reviewers ask, “What should we write down so the next person doesn’t have to ask?” Gatekeeping reviewers ask, “Why didn’t you already know this?” The first question creates documentation. The second question creates silence.

If your team’s documentation is stale, look at your code review culture. Teams that treat review as a teaching interface update their docs as a side effect. Teams that treat review as a quality gate let the docs rot, because the gate only checks the code, not the context around it.

Incident Response Patterns and Review Culture

There’s a direct line between code review culture and incident response. Teams with gatekeeping reviews tend to have blame-oriented postmortems. The same people who hold the approval power also hold the retrospective microphone. The incident is framed as a failure of the author, not a failure of the review system that let the bug through.

Teams with mentoring reviews tend to have blameless postmortems, because the review process already distributes responsibility. If a bug ships, the question isn’t “Who wrote this?” but “What signal did we miss in review, and why was the review not structured to catch it?” That’s a system question. It leads to better tests, better runbooks, and better review checklists. It doesn’t lead to a scapegoat.

Watch how your team reacts to a production incident. If the first question is “Who approved this?” you have a gatekeeping culture. If the first question is “What did we not know?” you have a mentoring culture. The code review process is the upstream cause of that downstream behavior.

Meeting and Decision Artifacts

Code review is a decision-making process. Every approval is a decision. Every request for changes is a decision. Every stale review is a decision, even if it’s a decision by omission. Teams that understand this keep decision artifacts: design docs, ADRs, review checklists, and explicit definitions of done. Teams that don’t understand this keep only the diff and a vague memory of “we talked about it in standup.”

Mentoring systems make decisions legible. They write down why a particular pattern was chosen, why a particular library was rejected, why a particular test was considered sufficient. Gatekeeping systems keep decisions in the reviewer’s head. The author is expected to absorb the decisions through repeated rejections. That’s not mentoring. That’s hazing with extra steps.

If you want to know whether your team is mentoring or gatekeeping, ask a new engineer to explain why a particular code review comment was made six months ago. If they can find the answer in a doc, you have a mentoring system. If they have to ask the person who wrote the comment, you have a gatekeeping system with a bus factor of one.

Operational Signals Checklist

Here’s a concrete checklist for diagnosing your team’s code review culture. Each item is an operational signal, not a personality judgment.

  • Review latency: Median time from PR open to first review. If it exceeds your team’s stated expectation, the system is broken.
  • Comment-to-merge ratio: Total review comments divided by merged PRs. A high ratio with low rework means nits are noise. A high ratio with high rework means the review is a design forum.
  • Approval distribution: Percentage of PRs approved by each engineer. A concentration above fifty percent for any single engineer is a bottleneck.
  • Rework cycles: Number of PRs with three or more review rounds. If this number is rising, the review process is failing to transfer knowledge.
  • Documentation freshness: Percentage of review comments that result in a doc update. If it’s near zero, the review isn’t creating durable knowledge.
  • Incident follow-up: Percentage of postmortems that reference a review gap. If it’s near zero, the review process isn’t being treated as a system.

Collect these numbers for two weeks. Don’t announce a new policy. Just observe. The pattern will tell you whether your team is mentoring or gatekeeping, and it will tell you without a single awkward conversation about feelings.

What Mentoring Actually Looks Like in a Diff

Mentoring through code review isn’t about being nice. It’s about being specific, timely, and willing to let go. A mentoring reviewer does the following:

  • Responds within the team’s agreed latency window, or explicitly hands off the review.
  • Separates blocking issues from non-blocking nits, and uses a consistent prefix system.
  • Explains the “why” behind a comment at least once, then links to the doc on subsequent occurrences.
  • Approves the diff when the blocking issues are resolved, even if they would have written it differently.
  • Offers to pair on the change if the diff needs more than two rework cycles.
  • Updates the relevant doc or ADR when a review reveals a gap in shared knowledge.

None of this requires charisma. It requires discipline. The discipline to treat review as a system with inputs, outputs, and service-level objectives. The discipline to measure the system instead of blaming the people in it.

What Gatekeeping Actually Looks Like in a Diff

Gatekeeping isn’t always loud. It’s often quiet, patient, and dressed in the language of quality. A gatekeeping reviewer does the following:

  • Leaves the PR unread for days, then requests changes on a deadline.
  • Leaves comments that are really questions about the author’s competence, not the code.
  • Requests changes on style preferences that aren’t in the team’s style guide.
  • Approves only after the author has rewritten the diff to match the reviewer’s personal architecture.
  • Never approves a diff from a junior engineer without a second senior sign-off, even for trivial changes.
  • Treats every review as a chance to demonstrate expertise, not to build it in others.

The gatekeeper’s defense is always the same: “I’m just maintaining quality.” But quality is a system property, not a personal property. A system that depends on one person’s vigilance isn’t a quality system. It’s a single point of failure with a hero complex.

The Cost of Gatekeeping

Gatekeeping has measurable costs. It slows cycle time, which delays value delivery. It concentrates knowledge, which increases bus factor. It discourages junior engineers, which increases turnover. It creates a culture of learned helplessness, where authors stop thinking and start guessing what the reviewer wants. It turns code review from a learning loop into a compliance ritual.

The cost isn’t just emotional. It shows up in deployment frequency, incident rates, and onboarding time. A team with a gatekeeping review culture will deploy less often, because every deploy requires a blessing. It will have more incidents, because the review process is optimized for style, not correctness. It will take longer to onboard new engineers, because the only way to learn the unwritten rules is to violate them and get rejected.

If you’re an engineering manager, these costs are on your budget. If you’re a senior IC, these costs are on your calendar. Either way, they’re not abstract. They’re the reason your team feels slow, brittle, and afraid to ship.

How to Shift from Gatekeeping to Mentoring

The shift doesn’t require a cultural transformation program. It requires three operational changes.

First, make review latency visible. Add a dashboard or a bot that reports median review latency per engineer. Don’t use it to shame people. Use it to start a conversation about capacity. A reviewer who takes three days to respond is either overloaded or disengaged. Both are system problems, not personality problems.

Second, separate blocking and non-blocking comments. Adopt a prefix convention and enforce it in the review tool. If a comment isn’t blocking, it shouldn’t block the merge. If a reviewer wants to block on a style preference, they must first get that preference into the style guide. That single rule eliminates most gatekeeping behavior, because it forces preferences to become policy or disappear.

Third, rotate review authority. Every engineer above a certain level should be able to approve any diff in their area. Pair senior reviewers with mid-level reviewers for a quarter. Let junior engineers approve trivial changes and then review their approvals in a weekly sync. The goal is to distribute the bus factor, not to lower the quality bar. Quality will improve, because more people will understand why the bar exists.

These changes aren’t expensive. They aren’t risky. They’re just boring operational work. That’s why most teams don’t do them. Boring operational work is the difference between a system that runs and a system that limps.

FAQ

How do I tell if a code review comment is mentoring or gatekeeping?

Look at the comment’s effect on the author’s future behavior. A mentoring comment teaches a principle that applies beyond the current diff. A gatekeeping comment enforces a preference that applies only to the current reviewer. If the comment could be replaced by a linter rule or a style guide entry, it’s gatekeeping. If it explains a tradeoff or a system constraint, it’s mentoring.

What is a healthy code review latency for a team?

There’s no universal number, but a useful baseline is same-day first response for PRs under 400 lines, and 24 hours for larger changes. The key isn’t the absolute number but the consistency. A team that reviews in two hours on Monday and three days on Thursday has a capacity problem, not a latency problem. Measure the distribution, not just the median.

Can a senior engineer be a gatekeeper without realizing it?

Yes. Most gatekeeping isn’t malicious. It’s the natural behavior of an expert who has internalized a set of rules and forgotten that those rules were never written down. The expert experiences their preferences as objective quality standards. The junior engineer experiences them as arbitrary rejections. The fix isn’t to blame the expert but to externalize the rules: write them down, agree on them, and let the linter enforce them.

How does code review culture affect incident response?

Code review culture sets the default for how the team assigns responsibility. A gatekeeping culture assigns responsibility to individuals: the author who wrote the bug, the reviewer who missed it. A mentoring culture assigns responsibility to systems: the review checklist that lacked a security check, the test suite that lacked a regression case. Blameless postmortems are only possible in a mentoring culture, because the team has already practiced asking system questions instead of person questions.

The next time you open a pull request, look at the review thread not as a conversation but as a log file. The latency, the comment density, the approval pattern, the rework cycles. That log file is telling you whether your team is building engineers or building bottlenecks. The difference isn’t in the code. It’s in the system around the code.

Two engineers reviewing code on a monitor during a collaborative session
Close-up of a code review interface with comments and diff view
Team discussing a pull request around a whiteboard with diagrams