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 --noEmitcatchesanytypes and unsafe casts- Gate 2: ESLint with
--max-warnings 0makes 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.
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.
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.
Related Tutorials
- AI Code Review for TypeScript: CodeRabbit + GitHub Actions
- AI coding tools 2026
- blocking AI crawlers
- Cursor AI TypeScript tips