CI/CD quality gates, sometimes written CICD gates, are automated checks wired into your continuous integration pipeline that fail a pull request the moment generated code violates a defined standard, before a human reviewer ever looks at the diff. 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. A gate only helps if it actually inspects the files you think it does, which is what the lint coverage audit measures.
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 can't hand you a war story with a client name on it, so here's something you can check yourself instead: the array-index case below fails to compile in six lines.
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'd start from. Four gates, roughly 30 lines of YAML total.
TL;DR:
- Gate 1:
tsc --noEmitcatches unsafe casts and implicitany- 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 Does AI Code Need Automated Gates?
CodeRabbit's December 2025 report analysed 470 open source pull requests, 320 AI-co-authored and 150 human-only, and found AI-co-authored ones averaged 10.83 issues each against 6.45, about 1.7x. Logic and correctness issues rose by roughly 75%. 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, What Does the TypeScript Strict Compile Check Catch?
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 unsafe type assertions, implicit any under strict, 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
}
}
I won't put a percentage on how many bugs this gate stops, because I haven't measured one. Here's what I did measure. I compiled a six-line function, const first = items[0]; return first.name.toUpperCase();, with TypeScript 6.0.3. Under plain --strict it compiled clean. Adding --noUncheckedIndexedAccess produced error TS18048: 'first' is possibly 'undefined'. That flag is the one I'd argue does the most work against AI-generated code, because it forces items[0] to be treated as T | undefined instead of T, which catches the "assume the array isn't empty" pattern that generated code leans on.
See the strict TypeScript config for the full breakdown of what each strict flag catches.
Gate 2, What Do TypeScript-Specific ESLint Rules Catch?
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.
@typescript-eslint/no-explicit-any should be "error", not "warn" - a warning lets it accumulate across 50 files before someone notices.
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, Why Set a 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.
How do you roll a new coverage threshold into an existing repo without breaking every open PR overnight? Follow this order.
- Run
npx vitest run --coverageonce againstmainto record the current baseline, don't guess at the starting number. - Set the threshold a few points below that baseline so the gate passes today and only blocks regressions, not the entire history.
- Raise the threshold by 5 points every couple of weeks until you reach 80%, giving the team time to backfill tests gradually.
- Once at 80%, treat any further increase as a deliberate team decision, not a default you bump just because you can.
Gate 4, What Does Automated AI Code Review Catch?
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 haven't measured how often a review bot like CodeRabbit catches something the three mechanical gates miss, so I won't quote a rate. The class of problem is easy to describe, though: a missing null check on external data, or a test that only runs with valid inputs. That's where I'd expect the gate to earn its slot, and where I'd look first if you want to check it on your own PRs.
What Do the Four Gates Look Like at a Glance?
| Gate | What it catches | Runs on |
|---|---|---|
| 1. Strict compile check | Type errors, implicit any | Every push |
| 2. TypeScript-specific ESLint rules | Unsafe patterns the compiler allows | Every push |
| 3. Test coverage threshold | Untested code paths | Every push |
| 4. Automated AI code review | Design-level issues: missing null checks, happy-path-only tests | PRs only, qualitative not pass/fail |
How Do You Put the Full Workflow Together?
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@v7
- uses: actions/setup-node@v7
with:
node-version: 24
cache: npm
- run: npm ci
- name: TypeScript check
run: npx tsc --noEmit
lint:
runs-on: ubuntu-latest
needs: typecheck
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 24
cache: npm
- run: npm ci
- name: ESLint
run: npx eslint . --max-warnings 0
test:
runs-on: ubuntu-latest
needs: lint
steps:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: 24
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.
Which Tutorials Should You Read Next?
- AI Code Review for TypeScript: CodeRabbit + GitHub Actions
- AI coding tools 2026
- Cursor AI TypeScript tips
- Bundle size budgets in CI