A pull request gets approved in six minutes.
The summary looks clean. The AI assistant explains the change in polished prose. The diff is small. Type checks pass. Unit tests are green. CI/CD reports success across the board.
Then the deploy goes out, and users can no longer complete checkout in Safari after logging in with Google. The bug does not appear in the diff. It does not appear in the PR summary. It does not appear in unit tests because the failure only happens after an auth redirect, a cookie handoff, a lazy-loaded payment widget, and a browser permission prompt that stalls the next step.
Nothing in the review process was technically wrong. It was just aimed at the wrong surface.
That is the uncomfortable reality of modern software delivery: approval has become increasingly disconnected from runtime truth. And AI-assisted development is making the gap worse.
AI can produce code that is structurally plausible, stylistically consistent, and easy to justify in a review. It can generate tests that validate its own assumptions. It can produce PR descriptions that read like a staff engineer wrote them at 2 p.m. on a calm Tuesday. What it cannot do, by default, is prove that the system still works across the messy workflow boundaries where regressions actually happen.
If your team is still treating a pull request diff as the primary artifact of trust, you are reviewing a fantasy. The code may be valid. The branch may be green. The workflow may still be broken.
The problem is not bad code review. It is misplaced confidence.
Most teams do not believe diff review is perfect. They already know code review misses things. The deeper issue is that the software delivery stack keeps manufacturing confidence signals that look stronger than they are.
A modern PR usually includes:
- a generated summary of what changed
- syntax and type checks
- unit and integration test results
- linter output
- screenshots or video, sometimes
- maybe a preview deployment
- an approval from one or two engineers
That bundle feels rigorous. It is certainly better than shipping directly to main with no review. But notice what it mostly validates:
- the changed code is readable
- the changed code matches local intent
- known invariants still hold in controlled conditions
- nothing obvious broke in isolated test environments
What it does not validate is more important:
- whether the user can still complete the full workflow
- whether the workflow survives real browser behavior
- whether third-party systems still respond as expected
- whether async timing, permissions, redirects, and state transitions still line up
- whether the app still works under the environment conditions users actually hit
This matters because production regressions are often not code-path failures. They are workflow failures.
A function can return the right shape and still participate in a broken experience. A component can render correctly in isolation and still deadlock a multistep flow. An API can pass contract tests and still break because a cookie is not available after a cross-site redirect in one browser mode.
The failure is not in a line. It is in the sequence.
That distinction used to matter mostly for complex products. With AI-generated code, it matters for almost every team.
AI makes the review surface more persuasive, not more trustworthy
The most dangerous thing about AI-assisted development is not that it writes terrible code all the time. Often it writes code that looks reasonable enough to pass review quickly.
That changes the economics of review.
Historically, reviewers often used rough heuristics:
- Does this diff look handwritten by someone who understands the system?
- Are the edge cases considered?
- Is the scope narrow enough to trust?
- Does the explanation sound grounded?
Those heuristics were always imperfect, but they offered some signal because writing code and explaining it required real effort. AI weakens that signal. Now a PR can arrive with:
- a neat refactor
- excellent naming
- a confident summary
- generated tests
- comments that explain the intent
- all checks passing
Yet the code may still encode shallow assumptions about runtime behavior.
That is because language models are especially good at satisfying local expectations. They generate code that is plausible at the function, file, and diff level. But many regressions live outside that level entirely:
- a callback now fires before UI state settles
- a redirect target loses a parameter in one edge path
- a widget mounts twice after hydration
- a permission prompt steals focus and breaks keyboard submission
- a retry loop collides with optimistic UI logic
- a feature flag changes sequencing between environments
The PR looks coherent because coherence is exactly what AI is optimized to produce.
Trustworthiness is something else.
If anything, the rise of AI should make teams more suspicious of diff-only review, not less. When code generation becomes cheap, runtime verification becomes the scarce resource.
Why CI/CD, unit tests, and manual QA routinely miss these failures
Engineers often respond to this argument by saying, "That is what tests are for." Yes, but most tests still validate fragments of reality, not the full runtime experience.
Let’s break down the usual layers.
CI/CD is a transport pipeline, not a truth machine
CI/CD is essential. Fast feedback loops improve developer productivity. Automated gating prevents obvious breakage. Reproducible builds matter.
But CI/CD mostly answers questions like:
- Did the code build?
- Did the test suite pass?
- Did static analysis detect known issues?
- Can artifacts be packaged and deployed?
Those are important delivery checks. They are not evidence that user workflows still function.
A green pipeline often means one of three things:
- The tests do not cover the broken workflow.
- The environment in CI does not reproduce the relevant browser or service behavior.
- The workflow is too brittle, too stateful, or too expensive to run in the current pipeline.
Teams then treat the absence of evidence as evidence of safety.
That is how false confidence gets industrialized.
Here is a familiar anti-pattern in GitHub Actions:
yamlname: ci on: pull_request: push: branches: [main] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 - run: npm ci - run: npm run lint - run: npm run typecheck - run: npm run test - run: npm run build
This pipeline is not bad. It is just incomplete. It verifies repository health, not workflow integrity.
If your application depends on auth providers, browser APIs, region-specific config, feature flags, embedded widgets, payment providers, email links, or multi-page state transfer, this pipeline will miss entire classes of runtime regressions.
Unit tests validate logic in isolation, which is not where users live
Unit tests remain valuable. They are often the cheapest way to preserve invariants and catch straightforward breakage.
But unit tests also create one of the strongest illusions in software engineering: if enough low-level assertions are green, the feature must work.
Consider a React login continuation flow:
javascriptexport async function continueCheckoutAfterLogin({ authClient, cartClient, navigate }) { const session = await authClient.getSession(); if (!session) { navigate('/login'); return; } const cart = await cartClient.restoreCart(session.userId); if (!cart?.items?.length) { navigate('/cart'); return; } navigate('/checkout/shipping'); }
You can unit test this thoroughly:
javascriptimport { continueCheckoutAfterLogin } from './continueCheckoutAfterLogin'; test('redirects to login when no session exists', async () => { const navigate = vi.fn(); await continueCheckoutAfterLogin({ authClient: { getSession: async () => null }, cartClient: { restoreCart: async () => null }, navigate, }); expect(navigate).toHaveBeenCalledWith('/login'); }); test('redirects to shipping when session and cart exist', async () => { const navigate = vi.fn(); await continueCheckoutAfterLogin({ authClient: { getSession: async () => ({ userId: 'u1' }) }, cartClient: { restoreCart: async () => ({ items: [{ id: 1 }] }) }, navigate, }); expect(navigate).toHaveBeenCalledWith('/checkout/shipping'); });
Everything passes. But production still fails because:
- the auth provider completes the redirect before the cookie is readable
- Safari blocks storage in a specific cross-site scenario
- the cart restore call races with hydration
- the route transition happens before a pending loader resolves
- a browser extension or third-party script changes focus or timing
None of that is visible in the unit test because the unit test removed the environment. That is its job.
The issue is not that unit tests are wrong. It is that they cannot be the main trust signal for workflow correctness.
Manual QA is too late, too narrow, or too inconsistent
Many teams still rely on a human sanity check before release. That can catch a surprising amount, especially when the tester knows the product deeply.
But manual QA breaks down under current delivery speed:
- PR volume is too high
- environments drift
- workflows are long and stateful
- browser/device coverage is thin
- testers cannot reproduce every auth or third-party path
- results are rarely attached to the PR in a reusable way
Worse, manual checks are often summarized as a comment like "tested locally" or "works on preview." That is not evidence. That is folklore.
Once the release cadence increases and AI increases code throughput, manual QA becomes even less capable of serving as the final source of trust. The denominator grows faster than human attention.
The real failure modes live in workflow boundaries
If you study serious regressions, they often show up in the seams between systems rather than inside a single function.
Common examples:
Auth handoffs
SSO, magic links, OAuth redirects, passkeys, cross-domain cookies, CSRF tokens, and session restoration all depend on runtime context. A small change in route handling or state initialization can break the handoff while leaving the local code perfectly readable.
Async UI states
Loaders, optimistic updates, suspense boundaries, disabled buttons, double submissions, retries, and stale data races often fail only under real timing conditions.
Browser permissions
Notifications, camera, microphone, clipboard, geolocation, popups, and storage access can alter flow control in ways that do not show up in mocked tests.
Third-party widgets
Payments, chat, fraud tools, analytics, consent managers, captcha, maps, and embedded schedulers introduce timing, iframe, and initialization complexity that diff review barely touches.
Multistep journeys
Signup, onboarding, checkout, account recovery, document upload, scheduling, and subscription management all carry state across several transitions. One wrong assumption in step 2 may not explode until step 5.
These failures are not exotic anymore. They are the routine cost of shipping software in browsers, on networks, across vendors, under feature flags.
Yet most PR approval still happens while looking at a text diff and a green badge.
The core insight: approval should require runtime evidence, not just static plausibility
The fix is not to stop doing code review. The fix is to stop pretending code review alone is meaningful approval.
A pull request should answer two different questions:
- Is this change understandable and maintainable?
- Is there evidence the intended workflow still works in a realistic environment?
Traditional review handles the first question reasonably well. It handles the second badly.
So teams need to attach runtime evidence directly to the PR.
That means artifacts reviewers can inspect without reconstructing the whole system locally:
- browser automation traces
- session replays from test runs
- screenshots at workflow milestones
- network logs for critical transitions
- environment-aware end-to-end checks
- action timelines tied to a specific commit
- recordings of cross-browser execution
The standard should be simple: if a change affects a user workflow, the PR should include proof of that workflow running.
Not a promise. Not a summary. Proof.
What runtime evidence looks like in practice
A useful runtime artifact does three things:
- shows what the user or browser did
- shows what the application rendered and requested
- ties the result to the exact branch, environment, and commit
Playwright is one of the clearest ways to generate this evidence because it can capture traces, screenshots, video, and structured test results around realistic browser execution.
Here is a simple Playwright workflow test for an auth-to-checkout journey:
javascriptimport { test, expect } from '@playwright/test'; test('user can resume checkout after Google login', async ({ page, context }) => { await context.tracing.start({ screenshots: true, snapshots: true }); await page.goto('https://preview.example.com'); await page.getByRole('button', { name: 'Add to cart' }).click(); await page.getByRole('link', { name: 'Checkout' }).click(); await page.getByRole('button', { name: 'Continue with Google' }).click(); // In practice this may use a test identity provider or a controlled auth stub. await page.waitForURL(/checkout\/shipping/); await expect(page.getByRole('heading', { name: 'Shipping' })).toBeVisible(); await expect(page.getByText('Order summary')).toBeVisible(); await context.tracing.stop({ path: 'artifacts/checkout-auth-trace.zip' }); });
This is already better than a unit test for review purposes because it validates sequence, browser behavior, navigation, and rendered state.
But the real value comes when the artifacts are uploaded from CI and linked directly in the PR.
yamlname: workflow-checks on: pull_request: jobs: e2e-critical-paths: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 - run: npm ci - run: npx playwright install --with-deps - run: npm run start:preview & - run: npx wait-on http://localhost:3000 - run: npx playwright test tests/critical --reporter=line,html - uses: actions/upload-artifact@v4 if: always() with: name: playwright-artifacts path: | playwright-report/ test-results/ artifacts/
Now the reviewer can inspect what happened instead of trusting a green check in the abstract.
Add environment awareness or your workflow tests will still lie
There is another trap here: teams add browser tests, then assume the problem is solved. It is not.
A runtime check is only as useful as the environment assumptions it preserves.
If your preview app uses fake auth, local storage shortcuts, mocked widgets, disabled CSP, or test-only feature flags, you may still be certifying a fantasy.
The workflow checks that matter most are the ones closest to production constraints:
- real browser engines, not just headless defaults
- realistic cookie and storage behavior
- actual redirects where possible
- third-party sandbox integrations
- feature flags matching target environments
- seeded but production-like data
- region and locale-specific behavior where relevant
Here is a Playwright config that pushes closer to real conditions by covering multiple browsers and collecting artifacts only when helpful:
javascriptimport { defineConfig, devices } from '@playwright/test'; export default defineConfig({ testDir: './tests', timeout: 60_000, use: { baseURL: process.env.APP_BASE_URL || 'http://localhost:3000', trace: 'retain-on-failure', screenshot: 'only-on-failure', video: 'retain-on-failure', }, projects: [ { name: 'chromium', use: { ...devices['Desktop Chrome'] } }, { name: 'webkit', use: { ...devices['Desktop Safari'] } }, { name: 'firefox', use: { ...devices['Desktop Firefox'] } }, ], });
Cross-browser execution matters because many workflow regressions are not logical errors. They are environment-specific behavior mismatches.
That is exactly why diff review misses them.
Python example: validating API health is not enough
Backend teams sometimes assume this problem mostly belongs to frontend or browser-heavy products. It does not.
A lot of runtime regressions come from backend changes that preserve API correctness while breaking workflow semantics.
For example, a Python service might return technically valid responses but alter timing, ordering, or idempotency behavior in a way that breaks the client journey.
pythonfrom fastapi import FastAPI from pydantic import BaseModel import asyncio app = FastAPI() class JobRequest(BaseModel): user_id: str payload: dict jobs = {} @app.post('/jobs') async def create_job(req: JobRequest): job_id = f"job-{len(jobs) + 1}" jobs[job_id] = {'status': 'queued', 'user_id': req.user_id} asyncio.create_task(process_job(job_id)) return {'job_id': job_id, 'status': 'queued'} @app.get('/jobs/{job_id}') async def get_job(job_id: str): return jobs[job_id] async def process_job(job_id: str): await asyncio.sleep(2) jobs[job_id]['status'] = 'complete'
A unit test may confirm that /jobs returns queued and later becomes complete. But a client workflow can still regress if:
- polling intervals now exceed UX expectations
- completion ordering changes under concurrency
- retries create duplicate jobs
- eventual consistency breaks a follow-up action
- a frontend timeout fires before the backend settles
A better test for PR review ties backend behavior to the actual user-visible workflow, not just endpoint contracts.
pythonimport time import requests def test_job_completes_within_user_visible_threshold(base_url): response = requests.post(f"{base_url}/jobs", json={ "user_id": "u1", "payload": {"type": "export"} }) response.raise_for_status() job_id = response.json()["job_id"] deadline = time.time() + 5 while time.time() < deadline: status = requests.get(f"{base_url}/jobs/{job_id}").json()["status"] if status == "complete": return time.sleep(0.25) raise AssertionError("job did not complete within workflow threshold")
Even this is still not enough on its own, but it is closer to the thing users experience: not abstract correctness, but whether the workflow advances in time.
What to attach to pull requests before approval means anything
If you want PR approval to correlate with production safety, require branch-level runtime artifacts for user-impacting changes.
A strong baseline includes:
1. Critical-path workflow runs
For each high-value journey, run at least one branch-specific workflow check:
- login
- signup
- checkout
- onboarding completion
- password reset
- billing update
- file upload
- core dashboard action
The list should be short and ruthless. These are not exhaustive regression suites. They are approval gates for business-critical behavior.
2. Action traces
Reviewers should be able to open a trace and inspect:
- clicks
- inputs
- navigations
- requests
- responses
- console errors
- timing gaps
- DOM snapshots
This turns debugging from guesswork into inspection.
3. Visual evidence
Screenshots and video help reviewers spot issues that assertions miss:
- loading spinners that never disappear
- disabled buttons
- modals hidden behind overlays
- layout shifts that block interaction
- widget initialization failures
4. Environment metadata
Every artifact should state:
- commit SHA
- app environment
- browser/version
- feature flag set
- test account or dataset
- region/locale if relevant
Without that context, debugging gets ambiguous fast.
5. Failure-first artifacts
Do not only upload logs on total suite failure. A flaky workflow often leaves useful evidence even when retries eventually pass. Preserve enough artifacts to investigate instability, not just hard failures.
Tools comparison: what helps and what does not
No single tool solves this. The point is to match tools to trust boundaries.
GitHub/GitLab PR review
Useful for:
- maintainability review
- architectural discussion
- code-level feedback
- ownership and approval workflows
Not enough for:
- validating runtime behavior
- catching browser-specific issues
- proving multistep journeys still work
Linters and static analysis
Useful for:
- consistency
- unsafe patterns
- obvious bugs
- security smells
Not enough for:
- timing issues
- redirects
- permission prompts
- third-party initialization
Unit test frameworks (Jest, Vitest, Pytest)
Useful for:
- low-cost logic verification
- preserving contracts
- refactor safety
- fast feedback in CI/CD
Not enough for:
- user workflows
- environment-sensitive behavior
- system sequencing
Integration tests
Useful for:
- service boundaries
- API/database interactions
- internal wiring
Still limited for:
- real browser UX
- auth redirects
- third-party widgets
- true end-user behavior
Browser automation (Playwright)
Useful for:
- realistic workflow execution
- artifact capture
- cross-browser validation
- attaching evidence to PRs
- faster debugging of runtime regressions
Tradeoffs:
- slower than unit tests
- requires environment discipline
- can become flaky if poorly designed
Session replay and observability tools
Useful for:
- understanding production failures
- correlating frontend and backend behavior
- investigating environment-specific incidents
- shortening debugging loops after deploy
Tradeoffs:
- often reactive, not preventative
- privacy and retention concerns
- not always available in pre-merge environments
The right stack is layered: static checks for code quality, unit tests for invariants, integration tests for boundaries, browser workflows for approval evidence, observability for production truth.
Actionable practices for teams shipping AI-assisted code
If your throughput is increasing because AI helps generate code faster, your review model has to change. Here is the practical version.
Treat diffs as explanations, not proof
A diff answers what changed. It does not prove the application still works. Train reviewers to stop conflating the two.
Define critical workflows explicitly
Most teams vaguely know their important paths but have never named them. Write them down. Keep the list small. If a path losing functionality would trigger an incident, it deserves a branch-level runtime check.
Gate by workflow impact, not lines changed
A tiny diff can break revenue. A huge refactor can be harmless. Approval policy should care about user-impacting surfaces, not superficial scope.
Require artifacts for risky categories
At minimum, require runtime evidence when a PR touches:
- authentication
- routing/navigation
- async state management
- payment/billing
- third-party embeds
- browser APIs
- onboarding or checkout steps
- feature flags controlling user journeys
Make the artifacts reviewer-friendly
If reviewing evidence is painful, no one will do it. Link directly to traces, screenshots, and reports from the PR. Make the “what happened” path short.
Design tests around milestones, not implementation details
Good workflow tests assert outcomes users care about:
- did the user reach shipping?
- did the invoice download?
- did the uploaded file appear?
- did the password reset complete?
Bad workflow tests mimic implementation internals too closely and become brittle during harmless UI changes.
Use stable test data and controlled integrations
You want realistic conditions, not chaotic randomness. For external systems, prefer sandbox environments, seeded accounts, and deterministic identities.
Preserve traces on failure and on important passes
Some successful runs are worth keeping, especially for critical paths. They provide a known-good baseline for debugging later regressions.
Review flaky tests as reliability failures, not housekeeping
Flakiness means your evidence system is untrustworthy. That is not a cosmetic problem. It directly undermines approval quality.
Close the loop with production incidents
Every workflow regression that escapes should update the approval model:
- Which artifact would have exposed this?
- Which environment condition was missing?
- Which critical path should be added or improved?
That is how testing matures: not by writing more tests blindly, but by attaching review to real failure patterns.
A more honest PR template
Most PR templates optimize for change description. They should also optimize for runtime validation.
A better template includes sections like:
md## User-facing workflows affected - Checkout after login - Billing address update ## Runtime evidence - [Playwright trace: checkout-auth-trace.zip] - [HTML report: critical-path run] - [Safari screenshot: shipping step visible] ## Environment - Preview deployment: pr-1842.preview.example.com - Browsers: Chromium, WebKit - Feature flags: new_checkout=true - Auth mode: sandbox Google OAuth ## Risks not covered - Apple Pay widget not exercised in this PR environment - Mobile viewport not included in this run
This is more honest than the usual wall of generated prose because it states what was actually verified and what was not.
That honesty matters. Teams do not need more confidence theater. They need sharper boundaries around what approval means.
The cultural shift: stop celebrating green checks that do not map to user reality
A lot of engineering organizations have quietly accepted a broken proxy: if CI/CD is green and the diff looks fine, shipping is responsible.
It is understandable. Those are legible signals. They fit into existing tools. They scale better than asking every reviewer to boot the branch and click through the app.
But they also fail at the exact moment users need them most.
The goal is not more process. The goal is better evidence.
When approval depends partly on runtime artifacts, several good things happen:
- reviewers ask better questions
- authors think in workflows, not just files
- debugging starts from traces instead of Slack speculation
- CI/CD becomes tied to actual product behavior
- developer productivity improves because failures are easier to reproduce
- AI-generated code gets held to the same standard as handwritten code: does it work in context?
This is the practical response to AI in software development. Not fear. Not hype. Just stricter trust criteria.
If AI increases output, you need stronger runtime verification to match it.
Conclusion
The PR bot did not really approve your software. It approved a story about your software.
That story may be well written. The diff may be clean. The tests may be green. The branch may even be deployable.
None of that guarantees a user can still get through the journey that pays your bills.
Runtime regressions hide in auth handoffs, async UI timing, browser permissions, third-party widgets, and multistep transitions because those are properties of systems in motion, not code at rest. Diff-based review cannot see them. Static checks cannot see them. Unit tests usually abstract them away.
So stop asking pull requests to carry trust they cannot earn.
Use diffs for maintainability. Use CI/CD for delivery hygiene. Use unit tests for logic. But if a change affects a real workflow, require runtime evidence attached to the PR: traces, replays, screenshots, environment-aware browser checks.
That is what meaningful approval looks like.
Until then, a green checkmark is often just a well-formatted hallucination.
