CI/CD Pipeline Optimization with Claude Code

Written by Michael Lip · Solo founder of Zovo · $400K+ on Upwork · 100% JSS Join 50+ builders · More at zovo.one

The Problem

Your CI/CD pipeline takes 15+ minutes to run. Developers wait for builds, merge queues back up, and deployment velocity drops. You know the pipeline could be faster but optimizing it requires understanding caching, parallelization, job dependencies, and build tool configuration.

Quick Start

Ask Claude Code to audit your pipeline:

Read my GitHub Actions workflows in .github/workflows/ and identify
optimization opportunities. Focus on:
- Jobs that can run in parallel
- Missing or misconfigured caching
- Redundant steps across jobs
- Slow steps that could be optimized
Give me a specific optimization plan with estimated time savings.

What’s Happening

CI/CD pipelines slow down as projects grow. Common causes include:

  1. Serial execution: Jobs that could run in parallel are chained sequentially
  2. No caching: Dependencies reinstall from scratch on every run
  3. Redundant work: The same build step runs in multiple jobs
  4. Over-testing: Every test runs on every PR, even when only docs changed
  5. Large Docker images: Build images include development dependencies
  6. Flaky tests: Failed tests trigger retries that double pipeline duration

Claude Code can read your entire pipeline configuration, understand job dependencies, and restructure the pipeline for maximum parallelism while maintaining correctness.

Step-by-Step Guide

Step 1: Baseline your pipeline performance

Ask Claude Code to add timing annotations:

Add step-level timing to my GitHub Actions workflow. I want to see
how long each step takes so we can identify the bottlenecks.

You can also check existing run times:

# List recent workflow runs with duration
gh run list --limit 10 --json durationMs,conclusion,displayTitle \
  --jq '.[] | "\(.displayTitle): \(.durationMs/1000)s - \(.conclusion)"'

Step 2: Parallelize independent jobs

A common before/after transformation:

Before (serial):

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
      - run: npm ci
      - run: npm run lint
      - run: npm run typecheck
      - run: npm run test
      - run: npm run build
      - run: npm run test:e2e

After (parallel):

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'
      - run: npm ci
      - run: npm run lint

  typecheck:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'
      - run: npm ci
      - run: npm run typecheck

  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'
      - run: npm ci
      - run: npm run test

  build:
    needs: [lint, typecheck, test]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'
      - run: npm ci
      - run: npm run build

  e2e:
    needs: [build]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'
      - run: npm ci
      - run: npm run build
      - run: npm run test:e2e

Lint, typecheck, and test now run simultaneously, cutting that phase from 3x to 1x duration.

Step 3: Optimize dependency caching

# Cache npm dependencies
- uses: actions/setup-node@v4
  with:
    node-version: 20
    cache: 'npm'

# For pnpm (requires pnpm setup first)
- uses: pnpm/action-setup@v4
  with:
    version: 9
- uses: actions/setup-node@v4
  with:
    node-version: 20
    cache: 'pnpm'

# Cache Turborepo build outputs
- uses: actions/cache@v4
  with:
    path: .turbo
    key: turbo-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}-${{ github.sha }}
    restore-keys: |
      turbo-${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }}-
      turbo-${{ runner.os }}-

Step 4: Share build artifacts between jobs

Instead of building in every job, build once and share:

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'
      - run: npm ci
      - run: npm run build
      - uses: actions/upload-artifact@v4
        with:
          name: build-output
          path: dist/
          retention-days: 1

  test-e2e:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/download-artifact@v4
        with:
          name: build-output
          path: dist/
      - run: npm run test:e2e

  deploy:
    needs: [build, test-e2e]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: build-output
          path: dist/
      - run: ./deploy.sh

Step 5: Skip unnecessary work with path filters

Do not run the full pipeline when only docs or configs change:

on:
  pull_request:
    paths-ignore:
      - '**.md'
      - 'docs/**'
      - '.vscode/**'
      - 'LICENSE'

# Or use path filters per job
jobs:
  test:
    if: |
      github.event_name == 'push' ||
      contains(github.event.pull_request.changed_files, 'src/') ||
      contains(github.event.pull_request.changed_files, 'tests/')

For monorepos, use Turborepo’s --filter flag:

- run: npx turbo run test --filter=...[origin/main]
  # Only tests packages changed since main

Step 6: Shard large test suites

Split tests across multiple runners:

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        shard: [1, 2, 3, 4]
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'
      - run: npm ci
      - run: npx jest --shard=${{ matrix.shard }}/4

Step 7: Fix flaky tests

Ask Claude Code to identify and fix flaky tests:

Analyze the test suite for flaky test patterns:
- Tests that depend on timing (setTimeout, Date.now)
- Tests with race conditions (parallel database access)
- Tests that depend on execution order
- Tests that use real network calls instead of mocks
List every instance and fix them.

Measuring Results

After optimization, compare pipeline duration:

# Compare before and after
gh run list --limit 20 --json durationMs,createdAt \
  --jq '.[] | "\(.createdAt): \(.durationMs/1000)s"'

Typical improvements:

Optimization Time saved
Parallel jobs 40-60%
Dependency caching 30-60 seconds per job
Build artifact sharing 1-3 minutes
Path filtering Skips entire pipeline for non-code changes
Test sharding Linear speedup with shard count

**Written by Michael** — solo dev, Da Nang, Vietnam. 50K+ Chrome extension users. $500K+ on Upwork (100% Job Success). Runs 5 Claude Max subs in parallel. Built this site with autonomous agent fleets. [See what I'm building →](https://zovo.one)

**Without a CLAUDE.md — what actually happens:** You type: "Add auth to my Next.js app" Claude generates: `pages/api/auth/[...nextauth].js` — wrong directory (you're on App Router), wrong file extension (you use TypeScript), wrong NextAuth version (v4 patterns, you need v5), session handling that doesn't match your middleware setup. You spend 40 minutes reverting and rewriting. Claude was "helpful." **With the Zovo Lifetime CLAUDE.md:** Same prompt. Claude reads 300 lines of context about YOUR project. Generates: `app/api/auth/[...nextauth]/route.ts` with v5 patterns, your session types, your middleware config, your test patterns. Works on first run. You commit and move on. That's the difference a $99 file makes. **[Get the CLAUDE.md for your stack →](https://zovo.one/lifetime?utm_source=ccg&utm_medium=cta-beforeafter&utm_campaign=claude-code-ci-cd-pipeline-optimization-guide)**
This took me 3 hours to figure out. I put it in a CLAUDE.md so I'd never figure it out again. Now Claude gets it right on the first try, every project. 16 framework templates. Next.js, FastAPI, Laravel, Rails, Go, Rust, Terraform, and 9 more. Each one 300+ lines of "here's exactly how this stack works." Copy into your project. Done. **[See the templates →](https://zovo.one/lifetime?utm_source=ccg&utm_medium=cta-deploy&utm_campaign=claude-code-ci-cd-pipeline-optimization-guide)** $99 once. Yours forever. I keep adding templates monthly.