Featured

Approval Drift: Why Code Changed After Approval Keeps Reaching Your Main Branch

An approved pull request isn't always approved code. Learn what engineering leaders on Reddit say about commits pushed after approval, self-merges and bypassed branch protection at enterprise scale, and how Keypup MCP uses approval dates, last commit dates, approval counts, required approvals, mergers, target branches and build status to measure how much code reaches your protected branches without anyone reviewing what actually shipped.

Arnaud Lachaume
Arnaud Lachaume LinkedIn
• 13 min read
Approval Drift: Why Code Changed After Approval Keeps Reaching Your Main Branch

Table of Contents

TL;DR: In large engineering organizations, a green "Approved" badge often covers code nobody reviewed. Commits are pushed after the last approval, pull requests merge with fewer approvals than required, and authors merge their own changes outside working hours. This is approval drift: the gap between the code that was approved and the code that shipped. Most review policies assume it doesn't happen; most audits find out it did. The Keypup MCP Server reads each pull request's last approval date, last commit date, last review date, approval count, required approval count, review state, author, merger, target branch, repository, lines changed, build status and merge date. Your AI assistant can then measure how much code reached your protected branches without a review of its final version, when and where it happens, who merges without a second pair of eyes, and which approved pull requests have changed since they were approved.

Every engineering organization has a rule that says code needs a review before it reaches production. Far fewer can say how often the code that reached production is the code that was reviewed. Between the reviewer's "LGTM" and the merge button, a lot can happen — and most dashboards only record that an approval exists.

The Friction: "Approved" Isn't the Same as "Reviewed"

In a team of six, everybody knows when a last-minute change slipped in after approval. In an organization with 1,500 engineers and 500 repositories, approval drift becomes invisible for structural reasons:

  • Approvals often survive new commits. Unless a repository is configured to dismiss stale approvals, an approval given on Tuesday still counts after the author pushes a 400-line change on Thursday.
  • Branch protection is configured repository by repository. Required approvals, admin bypasses and code owner rules drift apart as repositories are created by different teams over different years.
  • Exceptions become habits. "Just a typo fix after approval", "merging my own hotfix, nobody's online" — each one reasonable, together a sizeable share of what ships.
  • Audits sample; they don't measure. A SOX or SOC 2 auditor picks 25 changes and asks for evidence. Nobody knows the real rate across 60,000 merges until an auditor finds an exception.

The result: a review policy that looks enforced on paper, and a change history that tells a different story.

Why This Matters: The Real Cost of Approval Drift

The Risk Problem

The changes pushed after approval are rarely random. They are the last-minute fix, the "small" config tweak, the merge of a conflicting branch. These are exactly the changes that break production — and nobody but the author has read them.

The Compliance Problem

Separation of duties — the "four-eyes principle" — is a control in most SOX, SOC 2, ISO 27001 and PCI DSS programs. A self-merged pull request with no approval, or an approval that predates the final code, is an exception an auditor will ask you to explain. Explaining one is easy; explaining that you don't know how many there are is not.

The Trust Problem

When reviewers learn that what they approve isn't what merges, reviews become a formality. Authors learn that the approval is the gate, not the review. Both sides stop treating code review as the quality control it was designed to be.

The Enterprise Discussion: What Engineering Leaders Are Actually Saying

Stale approvals, admins bypassing branch protection and "approve now, I'll fix it after" come up again and again wherever engineering managers, staff engineers and security leads compare notes. The quotes below are representative of recurring threads on r/ExperiencedDevs, r/devops, r/EngineeringManagers and r/cybersecurity.

Staff Engineer, Banking Platform (r/ExperiencedDevs)

"Someone approved my PR, then I rebased and pushed a fix for the failing test, and it merged without anyone looking at it again. I only noticed because the fix was wrong. Our settings don't dismiss stale approvals and nobody had ever checked."

Engineering Manager, Enterprise SaaS Company (r/EngineeringManagers)

"Our SOC 2 auditor pulled 25 random changes and two were self-merged with no approval. Both were Friday evening hotfixes by senior engineers with admin rights. Now I need to tell them what the real rate is, and I have no idea."

Security Lead, Healthcare Group (r/cybersecurity)

"Every repo has branch protection, on paper. In practice half of them were set up years ago by different teams with different rules, and admins can bypass all of it. The policy says two reviewers. The data, if we had it, would say something else."

The pattern holds: leaders assume the review policy is enforced, but nobody measures whether the code that ships is the code that was approved.

How Keypup MCP Measures Approval Drift

The Keypup Model Context Protocol (MCP) Server gives your AI assistant structured access to the data Keypup imports from GitHub, GitLab, Bitbucket, Azure DevOps and Jira. For this analysis, what matters is each pull request's target branch and repository, its approval count and required approval count, its current review state, the date of its last approval, its last commit and its last submitted review, its author and the user who merged it, its lines changed, its build status and its merge date.

In the prompts below, the protected branches are the branches you name in the prompt — here main, master, production and release/* — not your platform's actual protection settings, which Keypup doesn't import. A pull request was changed after approval when its last commit is later than its last approval, and re-reviewed when a review was submitted after that last commit. Its final code was never reviewed when it merged with fewer approvals than required, or changed after approval without a later review. Durations are in business hours — Monday to Friday, 09:00 to 17:00 UTC by default; adjust the working hours and time zone to match your teams.

1. Running an Approval Integrity Health Check

The first question: how much of what reaches a protected branch was approved in its final form?

MCP Prompt:

For pull requests merged into main, master, production or
release branches in the last 12 months, show the number merged,
the share whose final code was never reviewed — fewer approvals
than required, or a commit after the last approval with no
review submitted after it — the share changed after approval,
and the share self-merged with zero approvals.

Output: Approval Integrity Health Check — Last 12 Months

Four KPI cards: 61,400 pull requests merged into protected branches in the last 12 months across 486 repositories; 19.2%, 11,790 pull requests, whose final code was never reviewed; 11.4%, 7,000 pull requests, changed after the last approval with no later review; 3.1%, 1,900 pull requests, self-merged with zero approvals

Key Insight: Almost one merge in five reached a protected branch without anyone approving the code that actually shipped. Most of it is not missing reviews: 7,000 of the 11,790 pull requests had an approval — for an earlier version of the code.

2. Checking Whether Approval Drift Is Getting Better or Worse

A single rate hides when the rules get bent.

MCP Prompt:

As a line chart, show by month of merge over the last 12 months
the share of pull requests merged into protected branches that
were changed after their last approval without a later review,
and the share merged with fewer approvals than required.

Output: Is Approval Drift Getting Better or Worse?

Line chart by month of merge from 2025-11 to 2026-10: share changed after approval and not re-reviewed between 10.2% and 13.8%, share with fewer approvals than required between 6.2% and 10.1%; both lines peak in December, March, June and September, the last month of each quarter

Key Insight: Both lines jump by about three points in December, March, June and September. Quarter-end deadlines, not individual habits, are when review rules get bent — which is also when the riskiest changes ship.

3. Finding Which Repositories Ship Unreviewed Code

An organization-wide rate hides the repositories where drift concentrates.

MCP Prompt:

For pull requests merged into protected branches in the last 12
months, show for each of these repositories the share that was
approved with no later change, changed after approval then
re-reviewed, changed after approval and not re-reviewed, and
merged with fewer approvals than required, as 100% stacked bars.
Add the number merged.

Output: Which Repositories Ship Unreviewed Code?

100% stacked bars of approval outcomes for eight repositories: payments-core 86% clean, 8% re-reviewed, 4% drifted, 2% under-approved, 6% unreviewed; ledger-service 8% unreviewed; identity-service 15%; checkout-web 22%; partner-api 24%; mobile-app 27%; data-platform 33%; infra-terraform 58% clean and 38% unreviewed, mostly under-approved

Key Insight: Unreviewed final code ranges from 6% in payments-core to 38% in infra-terraform. In checkout-web and partner-api the problem is drift after approval; in data-platform and infra-terraform it's merges with too few approvals. Same company, same written policy — very different results, which usually points to how each repository's branch protection is configured.

4. Seeing When Unreviewed Code Gets Merged

Does approval drift happen at random, or at specific times?

MCP Prompt:

As a heatmap, show the share of pull requests merged into
protected branches in the last 12 months whose final code was
never reviewed, by day of week and hour band of the merge in UTC.

Output: When Does Unreviewed Code Get Merged?

Heatmap of the share of merges whose final code was never reviewed, by day of week and three-hour band in UTC: Monday to Thursday working hours between 13% and 18%; Friday 15:00 to 18:00 at 31%, 18:00 to 21:00 at 42% and 21:00 to 24:00 at 44%; Saturday and Sunday between 44% and 55% in every band

Key Insight: Monday to Thursday working hours sit at 13–18%. Friday from 18:00 jumps to 42%, and weekends run at 44–55%. Merges outside working hours are the ones nobody else is around to approve — exactly where on-call fixes and "quick changes" end up. The hours are in UTC: read them against where your teams actually work.

5. Measuring How Far the Code Drifted After Approval

Not all drift is equal. A follow-up commit five minutes after "approve with suggestions" isn't the same as a rewrite two days later.

MCP Prompt:

For pull requests merged into protected branches in the last 12
months that had a commit after their last approval and no review
after that commit, group them by business hours between the last
approval and the last commit — under 1, 1 to 8, 8 to 40, 40 or
more. Show the number of pull requests as columns, the share
whose build status is not success as a line, and the median
lines changed.

Output: How Far Did the Code Drift After Approval?

Column and line chart of 7,000 pull requests changed after approval and not re-reviewed, by business hours between last approval and last commit: under 1, 3,710 pull requests, 8% with a build status other than success, median 180 lines changed; 1 to 8, 1,820, 13%, 240 lines; 8 to 40, 980, 22%, 410 lines; 40 or more, 490, 31%, 620 lines

Key Insight: Over half of the drift happens within an hour of approval, on the smallest pull requests — the profile of quick follow-up fixes. The 1,470 pull requests changed a full working day or more after approval are different: approved for one change, merged with another, and a quarter of them show a build status other than success. That's where a "dismiss stale approvals" rule pays for itself.

6. Seeing Who Merges Without a Second Pair of Eyes

The two repositories with the most under-approved merges deserve a closer look.

MCP Prompt:

For the data-platform and infra-terraform repositories, show
each person who merged pull requests into protected branches in
the last 12 months as a scatter plot: number of pull requests
merged against the share they authored and merged with zero
approvals. Size each point by the median lines changed. Hash
usernames.

Output: Who Merges Without a Second Pair of Eyes?

Bubble scatter of 12 anonymized mergers by pull requests merged and share self-merged with zero approvals, bubble size showing median lines changed: most mergers between 0% and 12%, median merger at 4%; Merger A at 38% with 410 merges and small pull requests; Merger B at 27% with 160 merges and the largest pull requests

Key Insight: Two accounts explain most of the self-merging. Merger A merges many small changes alone — worth checking against your list of automation and release accounts before treating it as a policy breach. Merger B self-merges large pull requests, which is the conversation to have first. Hashing usernames keeps the first look about the pattern, not the person.

7. Listing Approved Pull Requests That Changed Since

The last step turns the analysis into a list someone can act on before the next merge.

MCP Prompt:

List open pull requests targeting protected branches whose review
state is approved but which have a commit after their last
approval and no review submitted after that commit. Show the
repository and number, target branch, lines changed, approvals
against required approvals, business hours since the last
approval, business hours between the last approval and the last
commit, and build status. Sort by time since approval.

Output: Approved Pull Requests That Changed Since

Table of seven open pull requests in approved state with a commit after the last approval and no later review, sorted by business hours since approval: infra-terraform #2214 to production, 540 lines, 1 of 1 approvals, 31.5 business hours since approval, last commit 27.0 hours after approval, build failure; checkout-web #8841, 1,120 lines, 19.5 hours of drift, success; data-platform #5307, 860 lines, 16.0, no build status; partner-api #3092, 9.0, success; mobile-app #6455 to release/7.4, 6.5, in progress; identity-service #2390, 0.5; checkout-web #8856, 0.3
Pull RequestTarget BranchLines ChangedApprovals / RequiredSince Approval (bh)Approval → Last Commit (bh)Build Status
infra-terraform #2214production5401 / 131.527.0FAILURE
checkout-web #8841main1,1202 / 222.019.5SUCCESS
data-platform #5307main8601 / 117.516.0NONE
partner-api #3092main3101 / 112.09.0SUCCESS
mobile-app #6455release/7.42051 / 18.56.5IN_PROGRESS
identity-service #2390main752 / 25.00.5SUCCESS
checkout-web #8856main1402 / 23.50.3SUCCESS

Key Insight: Seven pull requests show as approved today, and none of them is exactly the code that was approved. The first four changed eight or more business hours after approval and one targets production with a failing build. These need a fresh review before anyone clicks merge; the last two changed within an hour of approval and need only a quick second look.

The Technical Implementation: What Keypup MCP Actually Reads

Every number above comes from fields Keypup already imports from your Git platforms. It helps to be precise about what they are — and what they aren't.

Drift Comes From Comparing Three Dates

For each pull request, Keypup records the date of the last approval, the date of the last commit and the date of the last submitted review. A last commit later than the last approval means the code changed after it was approved; a last review later than that commit means someone looked again. Any submitted review counts as a look, including a plain comment. Keypup compares dates — it doesn't diff the approved and merged versions — so a rebase, or merging the target branch into the pull request after approval, also counts as a change if it adds a commit with a later date. Measuring at the pull request level also means the Commits dataset, which carries each commit's size and message, can't be filtered by approval date on its own.

Approval Counts Depend on Your Platform's Rules

Each pull request carries its approval count and its required approval count, as exposed by your Git platform. If a repository has no required-review rule, its required count may be zero or a platform default: check how your repositories are configured before reading "fewer approvals than required" across all of them. Keypup doesn't import branch protection settings, admin bypasses or "dismiss stale approvals" options themselves — only their effect on each pull request.

Self-Merges Come From Author and Merger

Each merged pull request records its author and the user who merged it. A self-merge is a pull request where both are the same user. Bots and release accounts appear like any other user: filter them out by username. Keypup can hash usernames in a report, so you can look at the distribution before you look at names.

Protected Branches Are a Filter, Not a Setting

Keypup records each pull request's target branch and repository. "Protected branches" in this article means a list of branch names you put in the prompt; adjust it to your branching model.

Build Status Is the Current One

The build status is the latest status your Git platform exposes for the pull request — success, failure, in progress, action required or none. It isn't a history of every pipeline run, and pull requests in repositories without CI report none.

Business Hours and Merge Times Come From Formulas, Not a Calendar

Keypup's BUSINESS_HOURS function counts working time between two dates — Monday to Friday, 09:00 to 17:00 UTC by default — and accepts a different time zone, working hours and working days. Day of week and hour of merge are computed from the merge date. Neither knows about public holidays, on-call rotas or vacations: Keypup doesn't import calendars.

Real-World Impact: Enterprise Case Studies

Case Study 1: European Retail Bank

Before Keypup MCP:

  • 1,300 engineers across 450 repositories, all in scope for SOX change management
  • Two audit findings in a row for self-approved changes
  • No organization-wide measure of how often the review control was bypassed

After Keypup MCP:

  • The health check showed approval drift, not missing reviews, as the larger share of the exposure, which moved the fix from "remind people to review" to repository settings
  • The repository breakdown identified 60 repositories where pull requests required zero approvals; platform engineering standardized branch protection across all of them in one quarter
  • The share of merges with unreviewed final code fell from 17% to 5% in two quarters, and the next audit sample came back clean

"For years we answered the auditor with our policy. Now we answer with the rate, by repository, every month. The conversation changed from 'prove it' to 'show us the trend'."

— CISO, European Retail Bank

Case Study 2: North American Logistics Platform

Before Keypup MCP:

  • 800 engineers, 3,000+ pull requests merged a month
  • Production incidents traced back to "small" changes pushed after approval
  • On-call engineers with admin rights merging their own fixes at night and on weekends

After Keypup MCP:

  • The drift window analysis showed that changes made a working day or more after approval failed CI far more often; teams enabled "dismiss stale approvals" on all production repositories
  • The merge time heatmap made weekend self-merges visible; on-call fixes now get a mandatory next-business-day review, and the heatmap is rerun every month
  • Leads check the "approved but changed" list before each release, re-requesting review on anything that drifted

"We thought our problem was slow reviews. It was fast merges of code nobody had reviewed. The list of approved-but-changed pull requests is now part of every release checklist."

— VP Engineering, North American Logistics Platform

Implementation: Getting Started with Keypup MCP

1. Connect Your Git Platforms

Connect GitHub, GitLab, Bitbucket or Azure DevOps to Keypup. Pull requests, reviews, approvals, commits and merges are imported automatically.

2. Agree on What "Protected" Means

List the target branches that matter — main, production, release branches — and the bot and release accounts you want to exclude. Reuse the same list in every prompt so numbers stay comparable over time.

3. Check Required-Review Rules per Repository

Run the repository breakdown first. Repositories with a large share of "fewer approvals than required" merges often simply lack a required-review rule; fix the setting before you discuss behaviour.

4. Connect the MCP Server to Your AI Assistant

Follow the MCP documentation to connect Keypup to Claude, ChatGPT, Cursor or any MCP-compatible assistant.

5. Set a Baseline, Then Check Before Every Release

Run the health check and the monthly trend to set a baseline. Then bring the "approved but changed" list to every release, review the repository breakdown monthly, and share the trend with your compliance team each quarter.

Frequently Asked Questions

What is approval drift in code review?

Approval drift is the gap between the code a reviewer approved and the code that was merged. It happens when commits are pushed after the last approval and nobody reviews them before the pull request merges.

How do you detect commits pushed after approval?

Compare each pull request's last commit date with its last approval date. If the commit is later and no review was submitted after it, the final code wasn't reviewed. Keypup MCP does this from the dates it already imports for every pull request.

How do you measure self-merged pull requests?

Count merged pull requests where the merger is also the author, and check how many approvals they had. Keypup MCP reads the author, merger, approval count and required approval count of every pull request.

Does Keypup replace branch protection rules?

No. Branch protection prevents bypasses; Keypup MCP measures how often they still happen — by repository, branch, time and person — so you can see which rules are missing and show auditors the real rate.

The Bottom Line

An "Approved" badge only tells you that someone approved something. Keypup MCP shows how often what shipped is what was approved, and where it isn't:

✅ Measuring how much code reaches protected branches without a review of its final version ✅ Tracking approval drift month by month, including quarter-end spikes ✅ Comparing repositories to find missing required-review rules ✅ Finding when unreviewed code merges, by day and hour ✅ Separating harmless follow-ups from real drift by the time between approval and last commit ✅ Seeing who merges without a second pair of eyes, anonymized first ✅ Listing approved pull requests that changed since before anyone clicks merge

Ready to see how much of your shipped code was never reviewed in its final form? Connect the Keypup MCP Server and ask your AI assistant.

Ready to Transform Your Analytics?

Join teams already using AI to make data-driven decisions faster than ever.

Most Recent Articles

Timesheet Theater: Why Jira Time Logs Don't Match What Your Engineers Actually Built

Timesheet Theater: Why Jira Time Logs Don't Match What Your Engineers Actually Built

Software capitalization, client billing and R&D tax credits all run on Jira time logs that engineers fill in on Friday afternoon, from memory. Learn what engineering and finance leaders on Reddit say about time tracking nobody trusts, and how Keypup MCP compares Jira worklogs with linked pull requests, commits and merges to show when time is logged, how late it arrives, and which hours have code behind them.

Thomas Williams
The Hidden Queue: Why Most of Your Lead Time Happens Before the First Commit

The Hidden Queue: Why Most of Your Lead Time Happens Before the First Commit

Your pipelines got faster and your reviews got shorter, yet features still take months to reach customers. Learn what engineering leaders on Reddit say about work that waits weeks in Backlog, Ready for Development and To Do before anyone writes code, why delivery dashboards that start at the first commit miss it, and how Keypup MCP uses Jira workflow history, sprints and the commit and merge dates of linked pull requests to measure the queue in front of your code.

Liam Davis
Watermelon Status: Why Jira Says Done When Your Code Hasn't Merged

Watermelon Status: Why Jira Says Done When Your Code Hasn't Merged

Green on the outside, red on the inside. Learn what engineering leaders on Reddit say about Jira tickets moved to Done before the code ships, velocity that doesn't match delivery and status reports nobody trusts at enterprise scale, and how Keypup MCP compares each issue's closure date, workflow history and sprint with the state and merge dates of its linked pull requests to measure how much of your reported "Done" has no merged code behind it.

Stephane Ibos