Skip to content

CI/CD Pipeline Patterns

Your deploy passed every check and still took prod down. The unit tests were green, the Docker build succeeded, the kubectl set image returned 0 - but the image shipped a CRITICAL CVE in a transitive dependency, because nothing in the pipeline ever scanned it. CI told you everything was fine right up until pager duty told you it wasn’t.

The fix isn’t “write a better YAML by hand.” It’s using your AI tool to generate a pipeline that already has the gates a senior platform engineer would add - dependency caching, a vulnerability scan that actually fails the build, and a production deploy that waits on a real health check - then reviewing what it produced instead of authoring 200 lines of boilerplate from scratch.

  • A copy-paste prompt that turns an existing package.json + Dockerfile into a complete GitHub Actions workflow with matrix Node versions, dependency caching, and a Trivy scan that gates the build.
  • A prompt that converts a red CI log into a root-cause diagnosis plus a concrete fix, so you stop bisecting failed runs by hand.
  • A prompt for adding a metric-gated canary rollout to an existing kubectl deploy.
  • The three-tool workflow (Cursor, Claude Code, Codex) for generating, reviewing, and running pipeline changes - including the official GitHub Actions for Claude Code and Codex so the same model that wrote the pipeline can review your PRs.
  • A “When This Breaks” map of the failure modes generated pipelines hit first: cache misses, OIDC role-assume errors, rollout timeouts, and scanner false positives.

The Workflow: Generate a Pipeline From Your Repo

Section titled “The Workflow: Generate a Pipeline From Your Repo”

The trap with “create a CI/CD pipeline” prompts is that they produce a plausible-looking workflow disconnected from your actual project - wrong package manager, invented test scripts, a Node version you don’t run. Anchor the prompt to the files already in the repo so the output matches reality.

Open the repo in Cursor, add package.json, Dockerfile, and any existing .github/workflows/* to the Agent context with @, then run the prompt below in Agent mode. Cursor edits the workflow file in place and you review the diff in the editor before accepting. Keep package.json in context so it reads your real scripts block instead of guessing npm test.

A good generated result looks like this skeleton - note the scan step that actually gates the build, which is the part hand-written pipelines forget:

.github/workflows/ci.yml
name: CI
on:
push: { branches: [main] }
pull_request:
release: { types: [published] }
permissions:
contents: read
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix: { node: [20, 22] }
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node }}
cache: 'npm'
- run: npm ci
- run: npm test # the real script, read from package.json
build:
needs: test
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write # OIDC, no stored registry password
steps:
- uses: actions/checkout@v6
- uses: docker/setup-buildx-action@v4
- uses: docker/login-action@v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- uses: docker/build-push-action@v7
id: build
with:
push: true
tags: ghcr.io/${{ github.repository }}:${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Scan image (fails build on CRITICAL/HIGH)
uses: aquasecurity/trivy-action@v0.36.0
with:
image-ref: ghcr.io/${{ github.repository }}:${{ github.sha }}
severity: 'CRITICAL,HIGH'
exit-code: '1'
ignore-unfixed: true
deploy-prod:
needs: build
if: github.event_name == 'release'
runs-on: ubuntu-latest
environment: production # required reviewers enforce the gate
steps:
- uses: actions/checkout@v6
# ... cloud auth + rollout below

The reviewable decision here is exit-code: '1' on the Trivy step. Without it, the scan produces a report nobody reads and the build stays green. With it, a vulnerable base image blocks the deploy. That single line is the difference between the broken pipeline in the hook and a safe one.

Generating a pipeline is the easy half. The half that eats your afternoon is a run that fails on line 1 of a 400-line log with npm ci exiting 1 and no obvious reason. Paste the log at your AI tool instead of scrolling it.

The “passes locally but fails in CI” framing matters: it pushes the model toward environment-specific causes - a stale actions/cache key restoring node_modules built against the wrong Node version, a lockfile committed out of sync with package.json, a missing service container - rather than generic “check your tests” advice.

Once the basic deploy works, the next request is usually “ship to 10% of traffic, watch error rate, promote or roll back automatically.” This is where a generated kubectl blob is dangerous if it’s not gated on a real signal - so make the gate explicit in the prompt.

Insist on “plain shell with explicit thresholds.” A canary that promotes on an opaque action input is unauditable; one where the 0.01 error-rate threshold is visible in the workflow is something your team can reason about and tune.

The same generate-then-review loop applies to other platforms - only the target file changes. The prompts above work verbatim if you swap “GitHub Actions / .github/workflows/ci.yml” for “.gitlab-ci.yml stages” or “a declarative Jenkinsfile.” Two platform-specific notes worth giving the AI:

  • GitLab CI: ask for built-in security templates (include: - template: Security/SAST.gitlab-ci.yml and Container-Scanning.gitlab-ci.yml) rather than hand-rolled scanner jobs - they’re maintained by GitLab and wire results into the MR widget. Pin job images to a real tag (node:22-alpine), and use cache:key:files: [package-lock.json] so the cache invalidates on lockfile change.
  • Jenkins: request a Jenkinsfile using parallel quality-gate stages and Docker agents, and have it call withSonarQubeEnv + waitForQualityGate abortPipeline: true so a failing quality gate actually stops the pipeline. Keep deploy logic in a shared library, not inline.

Closing the Loop: AI That Reviews Its Own Pipelines

Section titled “Closing the Loop: AI That Reviews Its Own Pipelines”

The strongest pattern is letting the same model that wrote the pipeline review the PR that changes it. Both vendors ship a first-party GitHub Action, so you don’t shell out to npm install -g and scrape git diff by hand.

Cursor doesn’t ship a CI action - it’s an IDE. The division of labor: use Cursor’s Agent and background agent to author and iterate on the workflow locally with full editor context, then let Claude Code’s or Codex’s GitHub Action handle the in-CI review step. Generate locally, review in CI.

  1. Cache restores but the build still recompiles everything. The actions/cache key didn’t change but the contents are wrong - usually node_modules cached against a different Node version, or a key missing the lockfile hash. Use a key like ${{ runner.os }}-node${{ matrix.node }}-${{ hashFiles('**/package-lock.json') }} and prefer actions/setup-node’s built-in cache: 'npm' (which caches the npm download cache, not node_modules) over caching node_modules directly.

  2. OIDC deploy fails with “Not authorized to perform sts:AssumeRoleWithWebIdentity”. The job has permissions: id-token: write but the cloud-side trust policy doesn’t allow this repo/branch. The fix is on the IAM role’s trust relationship (sub condition must match repo:org/name:ref:refs/heads/main or your environment), not in the YAML. Ask your AI to generate the trust policy from the workflow’s permissions and trigger, then verify the sub claim matches.

  3. kubectl rollout status hangs until the job times out. New pods aren’t going Ready - failing readiness probe, image pull error, or insufficient cluster resources. Add --timeout=120s to fail fast, then kubectl describe deployment/app -n <ns> and kubectl get events --sort-by=.lastTimestamp to see why. A rollout that never completes is a stuck deploy, not a slow one.

  4. Trivy fails the build on a CVE you can’t fix. A CRITICAL in a transitive dependency with no patched version yet. Don’t delete the scan step - that’s how the hook happened. Use ignore-unfixed: true (already in the skeleton) to skip CVEs with no fix, and a reviewed .trivyignore with the specific CVE ID and an expiry note for the rest. Blanket-disabling the gate is the wrong fix.

  5. The generated workflow pins actions to @master or @main. This is the single most common defect in AI-generated pipelines: it’s a supply-chain risk and non-reproducible. Pin every third-party action to a released tag (or a commit SHA for high-trust paths). Re-prompt with “pin all actions to released tags, no floating refs” or fix it on review.