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. This page breaks down the 5 high-frequency root causes and gives an agent-loop pattern that lets the AI fix its own type errors.
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 -n "as any\|as unknown as" src/ and count. More than 2 in an AI-authored block is almost always avoidance.
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.
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 or runtime “User is undefined.”
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 disables color (cleaner to paste back); --incremental false forces a fresh compile if you suspect .tsbuildinfo is lying.
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 Composer / Aider can run shell commands. Configure them so the AI must re-run tsc after each change:
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.
In Aider: --auto-test --test-cmd "npx tsc --noEmit".
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
| Code | Meaning | Fix direction |
|---|---|---|
| TS2322 | Type mismatch | Check both sides of the assignment; add an explicit type annotation |
| TS2339 | Property missing | Type is too wide; add a type guard / in check / assertion |
| TS2345 | Wrong arg type | Inspect the signature; fix the caller’s value |
| TS2554 | Wrong arg count | Library version changed; check the changelog |
| TS2531/2532 | null/undefined unhandled | Add if (x) guard / x?.foo / x! |
| TS7006 | Implicit any | Add an explicit parameter type |
| TS2304 | Cannot find name | Missing import / missing @types/* |
| TS1484 | Type-only import required | Change to import type |
| TS2741 | Missing required property | Inspect the object literal; add the field |
Prevention
- Put
tsc --noEmitin the AI’s agent loop and in pre-commit hooks; failing types block the commit - In
CLAUDE.md/.cursorrules: banas any, ban@ts-ignore, ban@ts-expect-errorwithout a reason comment - Set
"strict": trueand"noUncheckedIndexedAccess": trueintsconfig.jsonto 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 --extendedDiagnosticsoccasionally to catch AI-generated recursive unions that tank compile speed
Related
- AI code broke build
- AI pre-commit review workflow
- Claude Code SEO audit
- AI dependency upgrade workflow
- AI tests pass but feature broken
Tags: #AI coding #Debug #Troubleshooting