Skip to content

CI/CD Quality Gates - AI-Generated TypeScript Tests

AI coding tools introduce any types and skipped edge cases faster than humans can review. These 4 GitHub Actions quality gates catch them automatically.

· · 7 min read
GitHub Actions workflow running automated CI checks on a TypeScript project in a terminal window

Quick Take

AI coding tools introduce any types and skipped edge cases faster than humans can review. These 4 GitHub Actions quality gates catch them automatically.

CI/CD quality gates exist because AI-generated TypeScript code doesn't look obviously wrong. It follows patterns, uses the right library APIs, compiles. That's exactly the problem.

What it also does: introduce any types when inference gets hard, skip the error case in unit tests, generate as SomeType casts instead of proper validation, and assume array indexes are always safe. None of this fails a casual review. All of it causes production bugs. I've watched it happen on real projects, fast AI output followed by slow debugging.

A CI pipeline that treats AI commits exactly like human commits, no exceptions, is the only reliable defense. Not code review, not pair programming, not "being careful." Mechanical gates that run on every push.

This is the GitHub Actions workflow I use. Four gates, roughly 30 lines of YAML total.

TL;DR:

  • Gate 1: tsc --noEmit catches any types and unsafe casts
  • Gate 2: ESLint with --max-warnings 0 makes lint violations build-breaking
  • Gate 3: Vitest coverage threshold (80%) forces edge case tests
  • Gate 4: CodeRabbit on every PR catches design issues the mechanical gates miss

For the broader quality strategy these gates fit into, see Vibe Coding TypeScript.

Why AI Code Needs Automated Gates

CodeScene's 2026 research found that AI-generated code carries 1.7x more defects than human-written code, with type-related errors making up the majority. That's not a knock on the tools, it's a structural reality. AI models predict plausible tokens. They don't run your code mentally and verify edge cases.

Speed makes this worse, not better. A developer who writes 10 lines slowly is also thinking about error handling. A developer who reviews 200 AI-generated lines is pattern-matching, not auditing. The defects that slip through are exactly the ones that look fine on a quick scan: a missing null check, a function typed as any, a test that never calls the failure branch.

Automated gates don't get tired. They don't skim. They run the same checks on every push regardless of who wrote the code or how much coffee you've had.

Gate 1, TypeScript Strict Compile Check

The fastest gate is also the most effective. tsc --noEmit runs the TypeScript compiler over your entire codebase without emitting output files. No setup beyond a tsconfig.json. (TypeScript Compiler Options, 2024)

In GitHub Actions:

- name: TypeScript check
  run: npx tsc --noEmit

This catches any types, unsafe type assertions, missing return types, and the as SomeType casts that AI tools generate when they can't infer the correct type. For this gate to do real work, your tsconfig.json needs strict: true plus noUncheckedIndexedAccess:

{
  "compilerOptions": {
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitReturns": true
  }
}

In my experience, this gate alone eliminates roughly 40% of the type bugs AI tools introduce. noUncheckedIndexedAccess is the one that does the most work against AI-generated code, it forces items[0] to be treated as T | undefined instead of T, which catches the "assume the array isn't empty" pattern AI uses constantly.

See the strict TypeScript config for the full breakdown of what each strict flag catches.

Cardboard boxes moving along parallel conveyor belts in a warehouse
Photo by Getty Images on Unsplash

Gate 2, ESLint with TypeScript-Specific Rules

tsc checks types. ESLint catches patterns that compile fine but signal AI shortcuts. The critical flag here is --max-warnings 0: warnings become errors in CI. No exceptions. (GitHub Actions Documentation, 2024)

- name: ESLint
  run: npx eslint . --max-warnings 0

Three rules do the most work against AI-generated TypeScript. Add these to your .eslintrc:

{
  "rules": {
    "@typescript-eslint/no-explicit-any": "error",
    "@typescript-eslint/no-non-null-assertion": "error",
    "@typescript-eslint/explicit-function-return-type": "error"
  }
}

Here's the opinionated take: @typescript-eslint/no-explicit-any should be "error", not "warn". AI tools use any the moment a stricter type requires more reasoning. A warning lets it accumulate across 50 files before someone notices. An error means the PR doesn't merge. That's the only version of this rule that works.

no-non-null-assertion blocks the ! operator. explicit-function-return-type forces every function to declare what it returns. AI tools skip return types constantly, this rule makes that a build failure.

Gate 3, Test Coverage Threshold

AI coding tools generate tests. That's not the same as generating good tests. What they generate is happy-path coverage: call the function with valid inputs, assert it returns the expected output. The error cases, null input, network failure, empty array, malformed data, don't exist. If you're specifically testing AI-generated React components, the React component testing strategies guide covers the patterns that catch the gaps coverage thresholds alone won't expose.

Vitest's coverage thresholds fix this indirectly. Set a minimum and the build fails when coverage drops below it:

// vitest.config.ts
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    coverage: {
      provider: 'v8',
      thresholds: {
        lines: 80,
        functions: 80,
        branches: 75,
      },
      include: ['src/**/*.ts'],
      exclude: ['src/**/*.d.ts'],
    },
  },
});

GitHub Actions step:

- name: Test coverage
  run: npx vitest run --coverage

80% line coverage is a reasonable floor. It's not the number that matters, it's what happens when the number drops. The developer (or AI tool) has to write the missing tests. That's when the error paths get covered.

Is 80% the right threshold for every project? Probably not. But it's a defensible starting point, and you can raise it once the baseline is established.

An automated factory with multiple levels of conveyor lines routing parcels
Photo by Hyundai Motor Group on Unsplash

Gate 4, Automated AI Code Review

The first three gates are mechanical. They catch specific patterns with no judgment. This gate is different, it reads the code and flags design-level issues. For TypeScript projects on GitHub, CodeRabbit setup is the fastest path to get this running.

A minimal .coderabbit.yaml that focuses on TypeScript-specific patterns:

reviews:
  profile: "assertive"
  instructions: |
    Flag any use of `any` type or non-null assertion operators.
    Check that functions have explicit return types.
    Flag tests that don't include at least one assertion on an error path.
    Note missing input validation on function parameters that accept external data.

This gate runs on PRs only, not on every push. It's qualitative, not a hard pass/fail, but it surfaces the issues the compiler can't: a function that technically type-checks but has no input validation, a test that covers the success path but assumes the happy path is all that matters.

I've found CodeRabbit catches around 1 in 4 AI PRs doing something that passes all three mechanical gates but is still wrong. Usually it's a missing null check on external data, or a test that only runs with valid inputs.

Putting It Together, The Full Workflow

All four gates as a single GitHub Actions workflow. Save this as .github/workflows/quality.yml:

name: Quality Gates

on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  typecheck:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
      - run: npm ci
      - name: TypeScript check
        run: npx tsc --noEmit

  lint:
    runs-on: ubuntu-latest
    needs: typecheck
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
      - run: npm ci
      - name: ESLint
        run: npx eslint . --max-warnings 0

  test:
    runs-on: ubuntu-latest
    needs: lint
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: npm
      - run: npm ci
      - name: Test coverage
        run: npx vitest run --coverage

The jobs run in sequence: typecheck first, then lint, then test. Set up branch protection in GitHub Settings > Branches to require all three to pass before merge. CodeRabbit runs automatically on PRs once installed, no additional workflow configuration needed.

The whole thing is about 40 lines of YAML. It runs in under 2 minutes on a typical TypeScript project.

These gates don't slow down AI-assisted development, they make it sustainable. The alternative is accumulated technical debt: any types spreading through the codebase, untested error paths waiting to fail in production, and type assertions that mask real bugs. No AI tool reverses that easily once it's established. A 2-minute CI check on every push does.

Frequently Asked Questions

What is a CI/CD quality gate?
A quality gate is an automated check in your CI pipeline that fails the build when code doesn't meet a defined standard. For TypeScript projects, that means type errors, lint violations, or test coverage drops below a threshold. The GitHub Actions docs describe jobs and steps as the unit of CI automation, each gate runs as a separate job so you get precise failure messages.
Why do AI coding tools need stricter CI gates than human developers?
AI tools generate code fast, which means defects accumulate fast too. CodeScene's research found AI-generated code has 1.7x more defects than human-written code. More importantly, AI tools reach for any, non-null assertions, and happy-path-only tests the moment a stricter approach requires more tokens to generate. Mechanical gates in CI catch these patterns every single time, human reviewers don't.
What coverage threshold should I set for AI-generated code?
80% line coverage is a reasonable floor when AI tools are writing tests alongside code. The real issue isn't the number, it's that AI-generated tests almost exclusively cover the happy path. A threshold forces the question: does this test suite actually try the error case? Vitest's coverage thresholds fail the build when the number drops, which surfaces untested paths immediately.
Does adding these gates slow down AI-assisted development?
No, they shift friction earlier. A TypeScript compile check and ESLint run take under 60 seconds in GitHub Actions. What they prevent is a production bug that costs half a day to trace. I've run this setup on three projects now and the gates catch something real on roughly 1 in 4 AI-generated PRs. That's not overhead. That's the pipeline doing its job.