Attending Google Cloud Next? Qodo is giving away 2 golden tickets to meet Benson Boone & Weezer.
→ Enter the raffle

Shift-Left Code Review: How to Catch Issues Before Opening the PR (Not After)

TL;DR

  • In most teams, code changes are reviewed only after a pull request is opened, when the change has already been committed and split across multiple commits.
  • Shift-left code review means reviewing your changes (the diff) while you’re still writing code in your editor, before they become a branch.
  • Reviewing the diff in the editor catches issues like missing auth on a route, unsafe retries in payment flows, or changes to shared APIs that break downstream services.
  • Qodo runs diff-aware checks on local changes and full branches, and enforces domain-specific rules (like payment safety or auth requirements) before PR review begins. In this blog, we’ll break down how shift-left review works, the four layers that matter, and how this fits into a development workflow.

In my experience as an SDE-2, a growing share of commits now include AI-generated code, increasing output without increasing review capacity. Teams are shipping faster, but the way code is reviewed hasn’t changed at the same pace. The result is simple: more changes reach pull requests without being fully validated.

I came across a discussion on r/devsecops that framed this problem more precisely. Shift-left works well when developers write and review their own code, you catch issues early and fix them while the context is fresh. But with AI-generated code, that feedback loop starts to break. The issue isn’t that CI/CD pipelines scanning stops working. It still catches problems. The issue is timing and context.

If a vulnerability or logic issue is introduced by generated code and flagged later in CI, the developer has often already moved on. The change is no longer fresh, and fixing it becomes rework instead of a quick correction. The connection between writing and validating the code is weaker because the developer didn’t fully reason through every line when it was generated. That same pattern shows up in code review.

In most teams, code changes are reviewed only after a pull request is opened. Until that point, there is no systematic validation of how the change affects the rest of the codebase, only local checks like linting or basic tests. A simple review workflow looks like this:

code changes are reviewed only after a pull request is opened

A developer completes a change, commits it, and opens a PR. Reviewers look at it hours or sometimes a day later, depending on the queue and availability. By then, the PR may include multiple commits, and the author may already be working on a different task.

When issues are identified at this stage, fixing them requires updating code that is already committed, sometimes across several files and commits. This leads to additional review cycles, back-and-forth comments, and delayed merges.

You can see the impact in team metrics:

  • Time to first review increases as PR queues grow
  • PRs require multiple revision cycles before merging
  • Review comments often focus on missing validation, edge cases, and basic safeguards, and in practice, a meaningful share of pull requests still contain high-severity issues that should have been caught earlier
  • Issues like missing authentication, unsafe retries, or broken state transitions are not caught during review and instead surface in production

This risk increases with AI-assisted development, where generated code introduces more surface area and makes it easier to miss issues during delayed review.

This is post-commit review that happens after code has already been packaged into a branch and shared with others. As PR volume increases, this model creates more review overhead and slower feedback cycles.

Shift-left code review changes this by moving validation earlier in the workflow. Instead of waiting for a pull request, the system analyzes the actual code changes (the diff) while the developer is still working locally. It evaluates how those changes interact with the rest of the codebase and flags issues before the code is committed and shared.

The result is a gap: more code is being produced than can be consistently validated before it reaches production, and traditional code review workflows are not designed for this level of output. This is where AI code review starts to matter, not as a replacement for reviewers, but as a way to evaluate changes earlier in the development workflow.

What Happens When Review Starts at the Pull Request

In most teams, review starts only after a pull request is opened. That means feedback arrives after the change has already been committed, split across multiple commits, and shared with others.

The Sample Flow of Post-Commit Review

Monday 10am:  Start feature
Monday 4pm:   Commit changes, open PR
Tuesday 2pm:  Reviewer finally looks at it
              "Missing auth middleware on new route"
              "Retry logic isn't idempotent for payments"
              "You changed the shared enum but didn't update callers"
Wednesday:    Context-switch back to this feature
              Fix issues, push again
              Wait for re-review
Thursday:     "Better, but you're still logging sensitive data"
Friday:       Merge

The feedback is valid, but it arrives after the change has already been committed and shared, so fixing it means modifying code that other work may already depend on.

By the time review happens:

  • the change is already spread across commits
  • the developer is working on something else
  • the branch may already be used as a base for other work

Fixing issues now means modifying code that has already been shared, which leads to additional commits, repeated review cycles, and delays in merging.

What Gets Missed in PR Review

Here’s what I see teams catch in PR review that should have been caught earlier:

Authentication gaps:

// Merged without anyone noticing missing auth
app.post('/api/refund', processRefund);

Unsafe retry logic on payments:

// Retries without idempotency = duplicate charges
for (let i = 0; i < 3; i++) {
  try {
    return await stripe.charges.create({ amount, customer });
  } catch (err) { continue; }
}

State transitions that violate business rules:

// Allows canceling already-cancelled orders
if (order.status === 'PAID') {
  await refund(order);
  order.status = 'CANCELLED';
}

Changed shared types, breaking downstream callers:

// Changed enum, forgot to update 3 services that use it
enum OrderStatus {
  CREATED = 'created',
  PAID = 'paid',
  CANCELLED = 'cancelled'  // newly added
}

All of these issues have something in common: they require understanding how the change affects the system, not just whether it passes linting. And all of them get caught during PR review, when fixing them is expensive.

Put multi-agent code quality to work in your stack

SEE QODO IN ACTION

What Shift-Left Code Review Actually Means

In many teams, “shift-left” ends up meaning stricter lint rules and more CI/CD pipeline checks. For example, developers run ESLint before committing (React), Pylint (Python), or Checkstyle (Java), and CI blocks merge if test coverage drops. These are useful controls, but they do not change when architectural and domain-level issues are discovered.

Linting and type checks are scoped to the file level; they are useful but shallow, detecting unused variables, incorrect types, missing imports, formatting issues, and some obvious anti-patterns. Static analysis may catch null pointer risks or deprecated APIs. All of that improves baseline code quality.

However, these tools do not reason about how a change affects the entire system.

They do not evaluate questions such as:

  • Did this change modify a public API that other services depend on?
  • Was authentication removed from a route during refactoring?
  • Does this retry logic apply to a non-idempotent payment call?
  • Is a new state transition allowed according to business rules?
  • Did this change weaken input validation on a critical endpoint?

Those are the kinds of issues typically discovered during pull request review, after the code is committed and shared. Shift-left code review changes when those issues are detected. Instead of waiting for a pull request, the review runs while the developer is still working locally. The system evaluates the actual diff, understands how the changed files connect to the rest of the codebase, and flags problems before the branch is pushed for team review.

The Four Layers of Shift-Left Code Review

The Four Layers of Shift-Left Code Review

To understand what shift-left actually requires, you need to separate review into distinct layers. Each layer answers different questions.

Layer 1: Syntax (Linting and Type Checking)

This includes linting, formatting, and type checking. It ensures the code compiles and follows basic rules. It answers mechanical questions: does this file meet style guidelines, and are types consistent? This layer is necessary but shallow.

Layer 2: Context (How Changes Affect the Codebase)

This layer analyzes the diff and its impact. It checks how the change interacts with existing code. For example, if a function signature changes, are all call sites updated? If a route handler changes, did its validation logic also change? If error handling was modified, are failure paths still covered? This requires understanding dependencies across files and modules.

Layer 3: Domain (Business Rules and Financial Safety)

This layer enforces business rules. These are rules that matter to production behavior, not just code structure. For example:

  • A refund should only be executed if the order is in a specific state.
  • Payment retries must be idempotent.
  • External service calls must have timeouts.
  • Sensitive fields must never be logged.

These rules are often enforced informally by experienced reviewers. Shift-left review encodes them so they are checked automatically.

Layer 4: Branch (Combined Impact of All Commits)

A single commit can look safe in isolation. Across a full branch, the combined changes may:

  • Modify shared interfaces.
  • Introduce inconsistent patterns.
  • Break assumptions in other services.
  • Drift from established architecture.

Branch-level review evaluates the entire diff against the main branch before a pull request is opened.

Shift-left code review means moving all of these checks, syntax, context, domain, and branch-level validation to occur before human PR review. The goal is not to eliminate human review. The goal is to ensure that by the time a pull request is opened, obvious implementation issues, domain rule violations, and cross-file inconsistencies have already been surfaced and addressed.

How Qodo runs shift-left review before the PR exists

Qodo runs shift-left review by analyzing code changes in the context of your entire codebase, not just the diff. Qodo’s Context Engine indexes your repositories, tracks how services and modules depend on each other, and builds a working model of your system. This allows each review to evaluate not just what changed, but how that change affects other parts of the codebase. On top of this, Qodo runs specialized review agents that operate at different stages of development, on local changes, across branches, and against domain-specific rules.

/review-uncommitted: Catch Issues Before the Commit using Qodo’s IDE plugin

How Qodo runs shift-left review before the PR exists

This runs on local changes before commit. When invoked inside the IDE, it analyzes the current diff against the working tree. It understands which files changed, what was added or removed, and how those changes interact with the rest of the codebase.

It does not only check formatting. It evaluates things like:

  • Did you modify a route handler but remove validation logic?
  • Did you introduce a new external call without a timeout?
  • Did you change a shared enum that other modules depend on?
  • Did you add a new endpoint without proper error handling?

Because it runs before commit, the developer fixes the issue while the code is still open and the context is fresh. There is no PR thread. No reviewer needs to point it out later. This is the context layer running locally.

/review-committed: Branch-Level Analysis Before the PR

/review-committed: Branch-Level Analysis Before the PR

This runs at the branch level, compares the current branch with the main branch, and evaluates the full diff across all commits. This is important because many architectural issues are not visible in a single local change. They appear only when you look at the combined branch delta.

For example:

  • A shared type was modified in one commit.
  • A dependent service was partially updated in another commit.
  • Error handling was removed in one file and not restored elsewhere.

Branch-level review surfaces these systemic issues before a pull request is opened. The branch can be hardened first, instead of exposing incomplete or risky changes to reviewers. This reduces review churn and avoids repeated review cycles.

Workflows: Domain-Specific Enforcement

Domain-specific enforcement is handled through Qodo workflows, which let you define how changes should be reviewed for specific use cases like payments, authentication, or compliance. These workflows are defined in natural language and stored as configuration files (for example, in .qodo/workflows/). They run automatically against code changes and flag violations based on the rules you define. These workflows operate at the execution layer, running checks on changes, while broader rule management (how standards are defined, tracked, and updated across teams) is handled separately.

Rather than relying on one-off scripts, rules in Qodo follow a lifecycle: they’re discovered from your codebase and PR history, measured for adoption and violations, and as your standards change. A payment integrity rule you define today becomes part of your living standards system, enforced on every commit, by every developer, without relying on the right reviewer being on the PR.

Creating a payment integrity workflow:

Creating a payment integrity workflow:

In Qodo IDE, type / and select “Create new workflow”:

  1. Name: payment-integrity-review
  2. Steps: Enter natural language instructions:
1. Review code changes for payment-related operations
2. Check that all payment retries include idempotency keys
3. Verify refunds only occur from allowed order states (PAID
4. Ensure external payment calls use centralized retry 
5. Flag any sensitive payment data in 
6. Provide findings with severity classification
  1. MCP Tools: Select filesystem, git for analyzing code changes
  2. Save to .qodo/workflows/payment-integrity-review.toml

Repository-level workflows:

Repository-level workflows:

When you save workflows to .qodo/workflows/ and commit them, they become available to your entire team automatically. Everyone working on the repository can run /payment-integrity-review without any setup. This ensures consistent enforcement across all developers. Teams using this approach avoid issues before they reach review ,in some cases, hundreds of issues per month that would otherwise show up in PRs or production.

Hands-on Example: Reviewing an Order Cancellation and Refund Change

To show how shift-left review works, let’s walk through a major change in a simple microservice-style backend:

  • An API Gateway handles authentication, rate limiting, and routing.
  • An Order service manages order state and metrics.
  • A Payment client calls an external payment service with retries and timeouts.
  • Metrics are stored in memory for simplicity.

The initial flow supported order creation and payment processing. Orders moved from CREATED to PAID after a successful charge. I added a new feature: order cancellation for paid orders, with an automatic refund.

The change included:

  • A new endpoint: POST /orders/:id/cancel
  • A new order status: CANCELLED
  • A refund method in the payment client
  • Updated metrics: cancelledOrders and refundedAmount
  • Logic in the order service to:
    • Validate that the order exists
    • Ensure only PAID orders can be canceled
    • Call the payment service to refund
    • Update the order status
    • Update metrics

Though this looks like a simple feature with a new endpoint, a state transition, and a call to the payment service. But, this touches multiple risk areas, such as:

  • State machine integrity (valid transitions)
  • Financial correctness (refund safety)
  • Retry semantics (idempotency)
  • Authorization
  • Observability and metrics consistency

This is the type of change that often looks straightforward in a PR but contains easy failure modes. For example:

  • What happens if the refund request times out but actually succeeds?
  • What if two cancellation requests arrive concurrently?
  • What if the order status is updated before the refund call fails?
  • What if retries trigger duplicate refunds?
  • What if metrics increment even when the refund fails?

These are production issues. This is exactly the type of feature that requires a shift-left review. In the next sections, we’ll look at what was caught by local diff review and what was caught by a domain-specific payment integrity workflow.

See how Qodo keeps AI-generated code production-ready

SEE QODO IN ACTION

What the Qodo’s IDE plugin Review Caught in This Change Before Commit

After implementing the cancellation and refund feature, the first step was to run /review-uncommitted inside the IDE before committing the changes. This runs against the current working diff. It evaluates only what changed locally, as shown in the snapshot below:

What the Qodo’s IDE plugin Review Caught in This Change Before Commit

Several issues were flagged immediately. Let’s go through some of them:

1. Trust Proxy Configuration Risk

Trust Proxy Configuration Risk

In the API gateway, app.set(“trust proxy”, 1) was enabled to correctly extract client IPs behind a reverse proxy. The review flagged that allowing trust proxy unconditionally can allow spoofed X-Forwarded-For headers if the application is not actually deployed behind a trusted proxy. If misconfigured, a client can control the reported IP-address.

This is not a syntax error. It is an environment-sensitive security risk that affects rate limiting and logging.

2. IP Extraction in Rate Limiter

IP Extraction in Rate Limiter

The rate limiter was updated to extract IPs from x-forwarded-for first, then fall back to req.ip. The review pointed out that blindly trusting x-forwarded-for without validating the proxy configuration can lead to IP spoofing. It also highlighted that the in-memory counter grows per IP and relies on a reset interval, which may behave poorly under hot reloads in development.

Again, this is implementation-level risk. It does not break compilation, but it affects production behavior.

3. Double Cancellation Vulnerability

Double Cancellation Vulnerability

In the order service, cancelOrder(orderId) allowed cancellation only if the status was PAID. However, the review highlighted that repeated calls to the same endpoint could trigger multiple refund attempts if concurrent requests hit before the state was persisted as CANCELLED.

There was no guard against duplicate refund execution beyond checking the current status. In a race condition, two threads could both observe PAID, and both attempt a refund. This is easy to miss in a manual review, which got flagged, and Qodo even gave an option to resolve. As shown in the fix below:

 Qodo even gave an option to resolve

On choosing the option to resolve, I got the suggested fix that added idempotency and a concurrency re-check to avoid duplicate cancellations/refunds on already-canceled orders.

4. Missing ID Validation

 

Missing ID Validation

In the controller, req.params.id was forwarded directly to the service without validation. If malformed IDs were passed, the service layer would handle it indirectly, but the controller did not enforce structure or return consistent error responses. This leads to inconsistent API contracts and makes error handling harder to standardize.

5. Metrics Accumulation Behavior

Metrics Accumulation Behavior

Metrics were extended to track:

  • cancelledOrders
  • refundedAmount

The review noted that metrics only accumulate and do not support reset or isolation for testing. Over long-running processes, this can skew monitoring or make test scenarios unreliable. This is not a critical bug, but it is an operational concern.

All of these issues were detected before the commit. None of them required a pull request discussion. The fixes were applied immediately, while the code was still open and the intent was clear. This is the value of local, diff-aware review. It catches implementation flaws early, before the branch grows and before reviewers have to spend time pointing out problems that could have been resolved locally.

In the next section, the focus shifts to domain-level issues flagged by a payment-specific custom workflow.

What the Payment Integrity Custom Workflow Caught

After resolving the local implementation issues, the next step was to run a domain-specific workflow:

What the Payment Integrity Custom Workflow Caught

The /payment-integrity-review workflow was designed specifically for financial operations. It focuses on charge, refund, retry, and state transition safety. The findings were more serious than the implementation-level issues.

1. Unsafe Retries on Non-Idempotent Operations

The payment client used a retry utility for both charge and refund calls. The workflow flagged that retries on non-idempotent operations can cause duplicate financial impact. If a refund request times out but succeeds on the provider side, a retry can issue a second refund.

The current implementation had no idempotency key and no protection against replay.

This is not theoretical. In real systems, network timeouts and partial failures are common. Without idempotency, retries can directly lead to monetary loss.

2. Missing Idempotency Keys in Payment Requests

The workflow detected that neither the charge nor the refund request included an idempotency key. Without idempotency keys:

  • Client retries can trigger duplicate operations.
  • Gateway retries can duplicate financial impact.
  • Concurrent requests can race.

In payment systems, idempotency is not optional. It is required to make retries safe.

3. Duplicate Request Vulnerability

The cancellation endpoint did not enforce request-level idempotency.

If a client repeated:

POST /orders/:id/cancel

There was no idempotency key at the API layer to ensure the operation would only execute once. Even with status checks, concurrent requests could both pass the PAID condition and attempt refunds. The workflow classified this as high risk because it directly affects financial integrity.

4. Unsafe State Transition Ordering

The service logic:

  1. Called refund.
  2. Updated order status.
  3. Updated metrics.

The workflow evaluated whether state updates were strictly tied to confirmed refund responses. If a refund succeeded but the service crashed before updating the order state, the system could later treat the order as still PAID and allow another cancellation attempt.

There was no reconciliation path to handle partial failure between external payment confirmation and local state persistence. This is a common failure mode in distributed systems.

5. Missing Reconciliation Strategy

The workflow also identified that there was no mechanism to detect mismatches, such as:

  • Payment provider shows refund succeeded.
  • Local order state remains PAID.

Without reconciliation, these inconsistencies accumulate silently. Over time, that leads to accounting drift.

6. Financial Impact of Incorrect Refund and Retry Handling

With style issues or validation gaps, these findings affect real money.

  • Duplicate refund: direct loss.
  • Duplicate charge: customer dispute.
  • Partial state update: inconsistent reporting.
  • Retry misconfiguration: financial leakage.

These issues are rarely caught by generic PR review unless a reviewer is actively thinking about financial invariants. Domain-specific shift-left review forces those checks to run every time. It does not rely on the reviewer’s memory or attention span. It encodes the invariants and evaluates them automatically before the PR is opened

FAQs

How is shift-left review different from just running linting?

Linting catches syntax and style issues at the file level. Shift-left review understands how your changes affect the system, missing auth on routes, unsafe retries on payments, broken validation logic, and state transitions that violate business rules. Linting says, “This variable is unused.” Shift-left review says “you removed authentication middleware during refactoring.”

What is shift-left code quality?

Shift-left code quality means moving quality checks earlier in the development process, before code is committed, not after it’s in a PR. Traditional approaches catch quality issues during PR review or QA testing, when fixing them requires context switches and rework. Shift-left catches issues while you’re still writing code in your editor. This includes not just syntax and formatting, but architectural problems, domain rule violations, and cross-file consistency issues. Qodo implements shift-left code quality through /review-uncommitted, which analyzes your local changes before commit, and custom workflows that enforce your team’s specific quality standards automatically.

What is the purpose of shift-left testing?

Shift-left testing aims to find bugs earlier when they’re cheaper to fix. Instead of discovering issues during QA or production, you catch them during development. The purpose is to reduce the cost and time of fixing defects by catching them when the context is fresh, and the code hasn’t propagated through your system yet. For code review specifically, shift-left testing means running domain-specific checks (like “payment retries must be idempotent”) before opening a PR. Qodo’s custom workflows make sure shift-left testing by letting you define test criteria as natural language steps that run on every code change in your IDE.

What tools support shift-left testing?

Qodo is the AI Code Review Platform built specifically for this. Three capabilities work together: the IDE Plugin catches issues before commit, such as missing auth, unsafe retries, broken validation, while the code is still open. The CLI Plugin runs branch-level analysis across all commits before a PR exists. The Rules System enforces your organization’s specific standards automatically, payment safety, state machine integrity, compliance requirements, discovered from your codebase and PR history, not written from scratch. The result: breaking changes, duplicated logic, and domain violations flag before reviewers ever see the branch. Across 2M+ installations and 4M+ PRs reviewed every year, that’s the pattern that holds.

What are the challenges of shift-left testing?

The main challenges are: running meaningful checks that understand system context (not just file-local patterns), avoiding false positives that frustrate developers and get ignored, catching domain-specific violations that vary by organization (payments, auth, compliance), and doing all this fast enough that developers actually run it. Generic static analysis creates too much noise. Custom scripts are brittle and hard to maintain. Qodo solves these challenges with context-aware analysis across your entire codebase, custom workflows you define in natural language that encode your specific rules, and fast execution (2-5 seconds) that fits naturally into the development loop without slowing you down.

How do teams share and enforce custom workflows across all developers?

A: Workflows are stored as configuration files committed directly to the repository. Any developer on the team can run them without additional setup. This means domain-specific rules, payment safety, auth requirements, and compliance checks are enforced consistently across the team, not just when the right reviewer happens to be on the PR.

Shift-left code review is not about replacing pull request review. It changes what PR review is used for. Instead of catching missing auth, unsafe retries, or broken state transitions, those checks happen earlier, while the code is still being written and before it is committed.

Qodo runs these checks on local changes and full branches, so issues are flagged before they reach your PR workflow. If your team is still catching basic issues during PR review, the feedback is arriving too late.

 

Start to test, review and generate high quality code

Get Started

More from our blog