Fast Feedback: Structuring Pull Request Pipelines for Speed and Quality

Claudio RomãoClaudio Romão
8 min read

When a developer opens a pull request, they’re looking for one thing: quick, clear feedback. They want to know — did I break something? Did I violate a rule? Is my code safe, secure, and well-tested?

The pull request (PR) stage is one of the most powerful checkpoints in the software delivery lifecycle. It’s where we catch bugs early, enforce quality standards, and shift security left — without slowing the team down. But to do that well, we need to design smart pipelines that prioritize speed and depth — not just one or the other.

In this post, we’ll look at how to structure your PR pipelines to be fast enough to feel invisible, yet powerful enough to maintain confidence in the code being merged. We'll share lessons learned from real-world teams, examples of good practices, and a few anti-patterns you should avoid.

In the next post, we’ll explore the “long game” — the deep, slow-running validations that don’t belong in your PR flow but still matter for stability and compliance. For now, let’s focus on building the kind of pipeline every developer loves: fast, predictable, and safe.

🎯 The Purpose of the PR Pipeline

The goal of your PR pipeline is to give developers confidence in their changes before merging, without introducing too much friction.

Not every check belongs in your PR pipeline — and trying to do too much here can backfire. That’s where the idea of push vs PR vs post-merge workflows becomes important. Because if your PR pipeline takes too long or is too flaky, developers will start to ignore it or find ways to bypass it.

What we really want is: catch problems early, without slowing developers down. The PR stage is a powerful opportunity to enforce your team's quality and security standards

Done right, it empowers developers instead of blocking them.

Here’s what a good PR pipeline should do:

  • Does it break tests?

  • Is it violating known risky patterns?

  • Are any secrets being leaked?

  • Is the code style clean and readable?

  • Are known vulnerable dependencies being introduced?

Here’s what a good PR pipeline should do:

1- Validate that the change is safe, secure, and aligned with standards:

This is your last checkpoint before merge — so it’s okay to be more critical, as long as you don’t block developers unnecessarily. At this stage, you should run:

🔸 Static Code Analysis (e.g., SonarQube or CodeQL)

  • Check for maintainability, duplications, complexity, and known security patterns

  • Sonar offers fast scan modes or PR-specific analysis

  • Focus on new issues only, not legacy debt

🔸 Dependency Scanning (e.g., Trivy, Snyk)

  • Alert if a vulnerable package is being introduced

  • Keep it focused on direct dependencies to avoid noise

  • Reserve full transitive scans for nightly or release pipelines

🔸 Secrets Detection (e.g., Gitleaks)

  • This is non-negotiable. Secrets should never reach version control

  • Fail fast, and surface clearly to developers in PR annotations

🔸 Linting & Format Checks

  • Fast, automated, and unopinionated. Run in seconds, catch formatting or code style issues early

🔸 Unit Tests

  • Validate core logic, not slow integration flows

  • Fail only on actual logic failures, not coverage deltas

  • Present the coverage percentage and indicate whether it meets the project’s threshold. If it falls below the minimum accepted level, surface a warning — but don’t block the PR (unless that’s your team’s policy).

🔸 Build the artifact (binary or Docker image)

  • At this point, it’s time to compile your code and generate an artifact (like a binary or container image). This will be reused in later stages such as integration testing, vulnerability scanning, and deployment previews

Security: Start Shifting Left — But Smartly

Security isn't just a separate step anymore — it's part of your quality pipeline.
At the PR stage, security checks should be present but lightweight:

  • ✅ Run a fast SAST tool like Semgrep, focused on the diff

  • ✅ Use GitHub Advanced Security (if available) to show annotations inline

  • ✅ Avoid running full OWASP ZAP, DAST, or license audits — save these for later stages

The rule of thumb:

If the risk is high and the check is fast — run it here.
If it’s a deep scan that might take minutes or hours, defer to a nightly or post-merge workflow.


🔁 But do I need to wait until PR to validate my code ?

We’re doing a lot in the PR pipeline — but do developers really need to wait until a PR is opened to validate their code? Absolutely not.

Developers should have checks running while they are still working — as they push code to their feature/ or hotfix/ branches. This gives them earlier feedback and helps prevent pipeline rework later.

The general purpose of push pipelines is to ensure:

  • The app hasn’t broken during development

  • Key functionality still works as expected (via unit tests)

  • Issues are caught early — before they pile up into a large, hard-to-review PR

💡 You don’t need to generate or save any build artifacts during push pipelines. These early checks exist purely to validate fast feedback gates — like linting, unit tests, and secret scanning. Building Docker images or storing binaries here is unnecessary and may slow your feedback loop.


⚙️ Conditional Logic and Selective Execution

Your pipeline doesn’t need to run everything on every change.epending on what changed, you can optimize what runs.

Use conditional execution based on paths, labels, or inputs to save time, For example

name: Conditional Step Example

on:
  pull_request:

jobs:
  validate-api:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Set list of changed files
        id: changed-files
        uses: tj-actions/changed-files@v44

      - name: Run task if API folder changed
        if: contains(steps.changed-files.outputs.all_changed_files, 'src/api/')
        run: echo "Changes detected in src/api/. Running task..."

This way, Python tests only run when Python files change, Terraform checks only run when .tf files are touched, and so on.


🧘‍♂️ Handling Feedback Without Blocking Flow

Not all feedback needs to block the PR immediately — but before merging, everything must be clean.

Some pipelines output helpful info — but don’t need to fail the build. Example:

  • Code coverage change

  • Dependency license warning

You can mark these jobs as non-blocking using continue-on-error: true or surface results via GitHub annotations.

Other task they must failt at this stage, like example:

  • SonarQube quality gate

  • Unit Tests - the complete version


🚀 Performance Tuning Tips

Speed matters. Here are a few ways to keep PR pipelines fast:

  • Use caching for build tools and dependencies (pip, npm, Maven, etc.)

  • Run matrix jobs in parallel (e.g., test on Python 3.8 and 3.10)

  • Avoid pulling unnecessary submodules or containers

  • Keep your test setup scripts clean and scoped


🧱 Template Architecture for PR Pipelines

When working at scale, you don’t want teams managing dozens of different CI files — and you don’t want every new security check to turn into a hundred manual PRs.

Instead, we build a centralized set of reusable workflow templates. Each type of check — like linting, unit testing, SAST, or dependency scanning — has its own standalone workflow file. Then, application repos simply call the templates they need.

This has a few major advantages:

  • ✅ Consistency across all repos

  • ✅ Simpler updates (e.g., add a new rule or fix a bug once)

  • ✅ Optional configuration via inputs: (e.g., language, tool version)

  • ✅ Easier to version and evolve gradually

For example, here’s how a PR pipeline might be defined using reusable jobs

name: PR Workflow
on: pull_request

jobs:
  lint:
    uses: org/.github/workflows/lint-template.yml@v1
    with:
      language: python

  unit-test:
    uses: org/.github/workflows/test-template.yml@v1

  sast:
    uses: org/.github/workflows/security-template.yml@v1
    continue-on-error: true

Every team benefits from consistency, and updates are easier to roll out. Because each job is modular and centralized, adding new checks is easy. Let’s say you want to: Enforce checkmark validation (e.g., block PRs without assigned reviewers or labels), add a job to verify that a changelog was updated, add an SBOM generator or a license scanner

Instead of changing 50+ repos, you can just:

  1. Add the new job to a shared PR workflow

  2. Or update a specific job template (e.g., security-template.yml)

  3. Let teams opt-in via with: inputs or if: conditions, depending on how flexible the templates are.

That’s the power of treating your pipelines as products — and applying good software design to your CI/CD architecture.

For a full example, check out this GitHub repository.


✅ Conclusion

Your PR pipeline is a guardrail — not a brick wall.
It should catch real issues, support developer flow, and build confidence in the code that’s about to land in main.

Use fast checks, skip unnecessary steps, and build in flexibility where possible. Don’t overload PRs with validations that belong in later phases — we’ll talk more about that in the next post.

Before we wrap up, here’s a quick comparison of what typically runs at each stage:

Check / TaskPush (Feature Branch)Pull Request (PR to Main)
✅ Linting & Formatting✔️ Yes✔️ Yes
✅ Unit Tests (Fast, Core)✔️ Yes✔️ Yes
✅ Secret Detection✔️ Yes✔️ Yes
❗ Static Code Analysis❌ No✔️ Yes (new issues only)
❗ Dependency Scanning❌ No✔️ Yes (direct only)
❗ Build Binary / Docker Image❌ No✔️ Yes
⚠️ Code Coverage❌ Optional⚠️ Optional (warn only)
⚠️ License Checks / SBOM❌ No⚠️ Optional
❌ Integration / DAST / E2E❌ No❌ No (run post-merge or nightly)

💡 Push pipelines are about developer speed and early signals.
PR pipelines enforce code quality, security, and release readiness.


👀 Up Next:

The Long Game: Post-Merge, Nightly, and Deep Validation Workflows
We’ll explore how and when to run the slower, more thorough tests — integration, security, DAST, SBOM, and full compliance pipelines.

0
Subscribe to my newsletter

Read articles from Claudio Romão directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Claudio Romão
Claudio Romão