Fix AI-Generated TypeScript Type Errors

AI code won't type-check (TS2322, TS2339, TS2554)? The exact tsc loop, error-code table, and agent rules that fix it in one pass.

Cursor / Claude Code / Copilot writes a chunk of TypeScript, you commit, then tsc --noEmit spits out a wall of TS2322: Type 'string' is not assignable to type 'number' or TS2339: Property 'foo' does not exist on type 'unknown'. LLMs have no runtime type reflection — they guess the shape of an interface from training data, which is fragile around generics, union types, and third-party .d.ts files.

Fastest fix: dump the complete error list with npx tsc --noEmit --pretty false > ts-errors.txt, paste it back verbatim with the rule “no as any, no @ts-ignore”, and make the agent re-run tsc after every edit until the count hits zero. The rest of this page breaks down the 5 high-frequency root causes, gives that self-verifying agent loop in full, and ends with an error-code-to-fix table.

Which bucket are you in?

Read the first error code in your log and jump straight to the cause:

First error you seeMost likely causeSection
TS2554 / TS2345 on a library callAI used an outdated API signatureCause 1
Compiles clean but crashes at runtimeAn as any / as unknown as is hiding itCause 2
never / unknown in the messageMissing generic parameterCause 3
TS2531 / TS2532 / TS18047strict null not handledCause 4
TS1484 or runtime “X is undefined”type/value import mixed upCause 5

Common causes

Ordered by hit rate, highest first.

1. AI guessed at a signature it hadn’t read

The most common failure: when calling a third-party library, the AI writes code matching a remembered older version, but you installed the new one and the API has changed.

// AI wrote (old Stripe SDK)
const charge = await stripe.charges.create({ amount: 1000, currency: "usd" });
// New SDK uses PaymentIntents
// → TS2554: Expected 0 arguments, but got 1

How to spot it: error codes are TS2554 (wrong arg count) / TS2345 (wrong arg type) / TS2339 (property missing), and the offending line is a third-party call.

2. Cast to any / as unknown as T hides the real error

When the AI can’t make types fit, it slaps as any or as unknown as Foo on. The compiler is happy; the runtime crashes.

const data = (response as any).user.email;   // response is actually { error: string }
// Compiles fine, runtime: TypeError: Cannot read property 'email' of undefined

How to spot it: grep -rn "as any\|as unknown as" src/ and count. More than 2 in an AI-authored block is almost always avoidance.

The right fix: validate the value with a type guard instead of asserting. When you genuinely need to check that an object conforms to a type without throwing away its inferred shape, use the satisfies operator (TypeScript 4.9+) rather than asas lets you lie to the compiler, satisfies does not.

// Avoid: as any silences a real error
const config = json as any;
// Better: satisfies validates the shape and keeps precise inference
const config = { port: 3000, host: "localhost" } satisfies ServerConfig;

3. Missing or wrong generic parameters

Array.prototype.reduce, useState, useRef, Map / Set, custom generic functions — AI often assumes TypeScript will infer correctly, but inference lands on never[] or unknown.

// AI wrote
const items = [].reduce((acc, x) => {
  acc.push(x.name);   // TS2339: Property 'push' does not exist on type 'never'
  return acc;
}, []);

// Correct
const items = [] as string[];
// or
const items = [].reduce<string[]>((acc, x) => { acc.push(x.name); return acc; }, []);

How to spot it: errors mention never / unknown / Argument of type 'X' is not assignable to parameter of type 'never'.

4. Strict mode null / undefined not handled

A lot of training data is from pre-strict-null code, so the AI assumes fields always exist. Turn on strictNullChecks and TS2532: Object is possibly 'undefined' is everywhere.

function getEmail(user: User | null) {
  return user.email;   // TS18047: 'user' is possibly 'null'
}

How to spot it: error codes TS2531 / TS2532 / TS18047 / TS18048 with "strict": true in tsconfig.json.

5. Mixing type and value imports / missing import type

With verbatimModuleSyntax or isolatedModules on, type-only imports must use import type. AI frequently forgets. This bites more now that verbatimModuleSyntax is the recommended default in modern setups (Vite, ts-node, Node’s native TS stripping), and erasableSyntaxOnly — the flag that enforces Node-strippable syntax — pairs with it.

import { User } from "./types";   // If User is type-only: TS1484 or runtime "undefined"
// Correct
import type { User } from "./types";

How to spot it: TS1484: 'User' is a type and must be imported using a type-only import when 'verbatimModuleSyntax' is enabled or a runtime “User is undefined.” For a whole-repo sweep, the community CLI (privatenumber) auto-rewrites the offending imports: run npx fix-verbatim-module-syntax --dry ./tsconfig.json to preview, then drop the --dry to apply.

Shortest path to fix

Ordered by ROI. Steps 1+2+3 are the standard fix loop for AI-generated type errors.

Step 1: Run tsc --noEmit to surface every error at once

Don’t re-prompt the AI on the first error you see. Get the whole list first so the AI sees the full picture:

npx tsc --noEmit --pretty false > ts-errors.txt
wc -l ts-errors.txt

--pretty false strips the color codes and the source-snippet framing, so every line is the plain file:line:col - error TSxxxx: message form that pastes cleanly into a prompt. If you suspect a stale cache is reporting errors you already fixed (TypeScript only writes .tsbuildinfo when incremental or composite is on), force a clean check: delete the cache file, or for project references run npx tsc --build --clean then npx tsc --build.

Step 2: Paste the verbatim errors (with file:line) back to the AI

Don’t paraphrase or summarize. The more complete the original, the more accurate the fix:

I ran tsc --noEmit and got the errors below. Please:
1. Do NOT use `as any` or `as unknown as`
2. Do NOT add // @ts-ignore / // @ts-expect-error to silence anything
3. If a third-party lib's types are broken, tell me which version to upgrade
   or which @types/* to install
4. Reply with a diff and explain each change

Error log:
src/api/user.ts:34:7 - error TS2322: Type 'string' is not assignable to type 'number'.
src/api/user.ts:41:12 - error TS2339: Property 'email' does not exist on type 'unknown'.
[paste in full]

Step 3: Put the AI in a self-verifying loop

Claude Code, Cursor, and Aider can all run shell commands, so don’t hand-feed errors one at a time — make the tool close the loop itself. The prompt that works:

For this task you must:
1. Fix the type errors
2. After each change, run `npx tsc --noEmit`
3. If errors remain, keep going. Max 5 iterations.
4. After 5, stop and tell me which errors you couldn't fix; paste them verbatim.

Per tool, as of June 2026:

  • Aideraider --auto-test --test-cmd "npx tsc --noEmit". After every edit Aider runs the command, and if it exits non-zero it feeds the output back and proposes a fix automatically.
  • Cursor — turn on Agent auto-run for safe commands so it can run tsc itself, or wire a Cursor Hook (Cursor 1.7+): register an afterFileEdit hook in .cursor/hooks.json pointing at a script in .cursor/hooks/ that runs tsc --noEmit and surfaces the errors back to the agent after each edit.
  • Claude Code — just tell it the loop above; it runs tsc in the integrated terminal between edits. Putting the type-check in a pre-commit hook makes the loop unavoidable.

Step 4: Wrong third-party types → find @types/* or upstream .d.ts

Many npm packages get community-maintained types:

npm install -D @types/node @types/react @types/lodash
# Does the library ship its own types?
npm view <pkg> types
# No @types and no bundled types → have the AI write a minimal declaration
echo 'declare module "untyped-lib";' > src/types/untyped-lib.d.ts

This beats as any because a module declaration can be filled in incrementally.

Step 5: Common TS error code → fix direction

CodeMeaningFix direction
TS2322Type mismatchCheck both sides of the assignment; add an explicit type annotation
TS2339Property missingType is too wide; add a type guard / in check / assertion
TS2345Wrong arg typeInspect the signature; fix the caller’s value
TS2554Wrong arg countLibrary version changed; check the changelog
TS2531/2532null/undefined unhandledAdd if (x) guard / x?.foo / x!
TS7006Implicit anyAdd an explicit parameter type
TS2304Cannot find nameMissing import / missing @types/*
TS1484Type-only import requiredChange to import type
TS2741Missing required propertyInspect the object literal; add the field

How to confirm it’s actually fixed

“Zero errors” is not the same as “fixed.” Run all three:

# 1. Clean type-check returns nothing and exit code 0
npx tsc --noEmit --pretty false ; echo "exit: $?"
# 2. No new escape hatches snuck in
grep -rn "as any\|as unknown as\|@ts-ignore\|@ts-expect-error" src/
# 3. The code actually runs (types passing != logic correct)
npm test   # or the smallest script that exercises the changed path

If step 1 is clean but step 2 finds fresh as any / @ts-ignore, the AI silenced the error instead of fixing it — reject and re-prompt. If you suppressed something deliberately, an @ts-expect-error with a one-line reason is far better than @ts-ignore, because it errors the day the underlying type gets fixed and the suppression becomes unnecessary.

Prevention

  • Put tsc --noEmit in the AI’s agent loop and in pre-commit hooks; failing types block the commit
  • In CLAUDE.md / .cursorrules: ban as any, ban @ts-ignore, ban @ts-expect-error without a reason comment
  • Set "strict": true and "noUncheckedIndexedAccess": true in tsconfig.json to force null handling
  • In code review, grep for as any / // @ts- with zero tolerance
  • When upgrading third-party deps, have the AI also re-check whether @types/* needs a matching bump
  • Run tsc --noEmit --extendedDiagnostics occasionally to catch AI-generated recursive unions that tank compile speed

FAQ

Why does the AI keep adding as any even after I tell it not to? Because casting always satisfies the compiler and the model optimizes for a green build. The fix that sticks is structural, not polite: put the ban in CLAUDE.md / .cursorrules, and add a grep gate (grep -rn "as any" src/ && exit 1) to your pre-commit hook so the commit physically fails. A rule the tool can’t bypass beats a request it can ignore.

My editor shows no errors but tsc does. Why the mismatch? The editor’s language server and your CLI tsc can run different TypeScript versions or different tsconfig.json files (a monorepo or a tsconfig.build.json is the usual culprit). Trust tsc --noEmit from the project root as the source of truth, and pin the workspace TypeScript version so the editor uses the same one.

Is // @ts-expect-error an acceptable fix? Sometimes — it’s strictly better than @ts-ignore because it also errors once the underlying problem is gone, so it can’t rot silently. Only use it with a comment saying why, and only when the type genuinely can’t be expressed (a known-broken upstream .d.ts, for example), not to dodge a real bug.

The library types are just wrong. What do I do? First check for a fix: npm view <pkg> types to see if it ships types, and install @types/<pkg> if the community maintains them. If both are missing, write a minimal declare module stub in a .d.ts and fill it in incrementally — far safer than as any because it’s scoped and you can tighten it later.

How do I stop this happening again on the next AI edit? Make the type-check non-optional, not a habit you have to remember. Run tsc --noEmit in your agent loop, your pre-commit hook, and CI; with "strict": true and "noUncheckedIndexedAccess": true in tsconfig.json, the compiler forces the AI to handle null and indexing up front.

Tags: #AI coding #Debug #Troubleshooting