DEV Community

Indra Gusti Prasetya
Indra Gusti Prasetya

Posted on Originally published at indragustiprasetya.com

Fix ESLint Cannot read properties of undefined (Intrinsic)

Two teams hit the same wall this month with the same tool and needed opposite fixes. One upgraded to TypeScript 7 and forced past a peer range that had told them no. The other never touched TypeScript: their package.json still pins typescript: ^6.0.3, and their package manager wrote TypeScript 7 into the tree anyway.

Short version: ESLint throwing Cannot read properties of undefined (reading 'Cjs') or (reading 'Intrinsic') means typescript-eslint loaded TypeScript 7, whose Go-native rewrite dropped the JS enum exports it reads. Run pnpm why typescript to see which of three trees you have. If the path runs through @typescript-eslint/types, pnpm's bundled compatibility database put it there, and editing your own package.json will not move it.

Two error strings, one missing enum

The crashes look like this:

TypeError: Cannot read properties of undefined (reading 'Cjs')
    at .../@typescript-eslint/typescript-estree/dist/create-program/shared.js:59:18
Enter fullscreen mode Exit fullscreen mode
TypeError: Cannot read properties of undefined (reading 'Intrinsic')
Enter fullscreen mode Exit fullscreen mode

Both are the same read against a different missing export. shared.js:59 reaches for .Cjs on a TypeScript enum object that came back undefined, and the 'Intrinsic' variant does the same thing one enum over. TypeScript 7.0 went GA on 8 July 2026 as the Go-native compiler and shipped with no stable programmatic API, so the JS module every TypeScript major had exposed for a decade is no longer there in the shape the linter expects. This is the same class of failure as the npm arborist edgesOut crash: a null-property read that is really a resolution problem three layers up.

The threads on this run the two cases together, which is why the popular fix works for half the people who try it.

Where TypeScript 7 came from if you never installed it

pnpm ships a compatibility database: a table of packageExtensions patches, maintained partly with Yarn as @yarnpkg/extensions plus pnpm's own curated entries, that rewrites third-party package.json files during resolution. One entry gave @typescript-eslint/types a dependency on typescript.

It started reasonably. typescript-eslint issue #3622, opened in 2021, correctly reported that @typescript-eslint/types reaches for a typescript module it never declares. The patch declared it, unpinned. For five years that resolved to whatever TypeScript major was current and nothing broke, because every major kept the same JS module surface. Then one did not.

The pnpm 12.0.0 release notes, published 27 August, state the outcome directly: the removed entries "named packages that are only imported for their types, so installing them was at best unnecessary and at worst broke the dependent: @typescript-eslint/types gained a typescript dependency resolved to the newest release, which put TypeScript 7 under older @typescript-eslint versions and made ESLint fail with 'Cannot read properties of undefined (reading Intrinsic)'."

flowchart TD
  A["your package.json\ntypescript: ^6.0.3"] --> B["pnpm resolution"]
  B --> C["compatibility database\npackageExtensions"]
  C -->|"injects typescript dependency"| D["@typescript-eslint/types"]
  D --> E["typescript@7.x\nGo-native compiler"]
  E --> F["require('typescript')\ninside typescript-estree"]
  F --> G{"enum exports present?"}
  G -->|"no"| H["TypeError:\nreading 'Intrinsic'"]
  G -->|"yes"| I["lint runs"]

Which of the three trees do you have

Run pnpm why typescript (or npm ls typescript) before you change a single line, and read the path.

What pnpm why shows Cause Fix that works
Path is your root devDependencies You upgraded and overrode the peer range Roll back to 6.x, or run TS 7 as a second non-blocking checker
Path is @typescript-eslint/types > typescript Compatibility database injected the edge pnpm 12.0.0, overrides, or ignoreCompatibilityDb=true
Two entries, TS 6 and TS 7 at different depths Hoisting resolution, common under node-linker=hoisted Dedupe, or pin via overrides so both depths agree

For the first row: typescript-eslint@8.63.0 declares a peer range of >=4.8.4 <6.1.0. Getting past that took --legacy-peer-deps, --force, or strictPeerDependencies=false. The range was accurate and the override turned a correct install-time refusal into a runtime TypeError several minutes into CI, which is the same trade the npm 12 install-script lockdown taught people to stop making. A flag that silences the message rarely removes the condition.

Then confirm which compiler the tool actually loaded, rather than the one you believe you installed:

node -p "const ts=require('typescript'); [ts.version, typeof ts.Extension, typeof ts.ScriptKind].join(' | ')"
Enter fullscreen mode Exit fullscreen mode

A 7.x version string with undefined for both enum types is the signature. Those objects are exactly what typescript-estree reads.

The pin that does nothing

The top-voted advice on every one of these threads is "pin typescript in your package.json" or "downgrade." On the injected path, the edge was written into a transitive package's manifest during resolution, so your root pin is not in the conversation. Neither is your .pnpmfile.cjs: the compatibility database is applied after it, so your own transformations cannot see or override the patch.

Three levers actually reach it, in the order I would try them:

  1. Upgrade to pnpm 12.0.0. It removes the statically-analysed entries while keeping the curated @yarnpkg/extensions ones. Read the pnpm 12 upgrade traps first, because the Rust CLI rewrite lands several unrelated breaks in the same bump.
  2. Add an overrides entry pinning typescript to your 6.x line. This wins over the injected version and works on any pnpm from 7 onward.
  3. Set ignoreCompatibilityDb=true in pnpm-workspace.yaml, available since pnpm 7.9.0. It works, and it disables every fixup in the table, including ones other packages in your tree quietly depend on. Use it to unblock a build today and upgrade this week.

A patch table with no Git diff

The interesting part of this failure is the supply-chain shape, and it deserves more attention than the workaround. A package manager added an edge to your dependency graph from a table compiled into its own binary. The edge does land in pnpm-lock.yaml, so it is technically visible, but it appears on a pnpm version bump with no package.json change, which is the diff every reviewer scrolls past.

Rush's documentation has flagged the structure for years, calling the fixups "hidden magic bundled into the PNPM binary with no Git diff visibility", coupled to whichever @yarnpkg/extensions snapshot that particular pnpm build happens to carry. Your dependency graph is a function of your lockfile and your package manager build. If you generate an SBOM from the installed tree, it will faithfully record TypeScript 7 as a real component of your project, sourced from nothing you ever wrote down.

Pin packageManager in package.json and treat a bump to it as a dependency change in review.

What actually links against tsgo today

There is a working path to type-aware linting on TypeScript 7 right now. oxc's tsgolint went stable on 22 July 2026, tracks TypeScript v7.0.2, and implements 59 of typescript-eslint's 61 type-aware rules. The oxc announcement reports 12-18x faster runs than ESLint with typescript-eslint across vscode, TypeScript, typeorm, and vuejs/core. It gets there by linking typescript-go directly instead of importing the JS module, which is why the API gap does not touch it.

The cost is two missing rules and a different config surface. If lint wall-clock is your CI bottleneck, that is a cheap trade. If your rule set is heavily customised, budget real time for the migration.

What 7.1 fixes on 10 November, and what it does not

typescript-eslint closed the TypeScript 7 support request (#12518) as not planned on GA day. The TypeScript 7.1 iteration plan lists "Stabilize API" covering Content Mapper, Emit, and Language Service, against a 7.1 stable target of 2026-11-10, roughly ten weeks out.

That date unblocks the version range. It does not finish the job. Per typescript-eslint #10940, tsgo is consumed over native or WASM bindings, ESLint core has no async parser support, and rules expect JS-land AST nodes. A stable API gives the maintainers something to build against; ESLint's synchronous rule model still has to meet a Go type checker somewhere in the middle. Plan on 7.x type-aware linting through the official parser arriving well after November.

The blast radius extends past ESLint for the same reason. ts-jest, ts-morph, API Extractor, and the template checkers behind Volar, Svelte, and Astro all consume that missing programmatic API.

Do this before your next CI run

  1. pnpm why typescript. Read the path and match it against the table above. Everything else is downstream of knowing which tree you have.
  2. If the path runs through @typescript-eslint/types, upgrade to pnpm 12.0.0 today. If a Rust-CLI break blocks that upgrade, add an overrides entry for typescript at your 6.x version and revisit the upgrade this sprint.
  3. Grep your CI configs and Dockerfiles for --legacy-peer-deps, --force, and strictPeerDependencies=false, and delete every hit that touches a TypeScript install. Each one converts an accurate refusal into a stack trace ten minutes later.
  4. Pin packageManager in package.json if it is not already, and add "pnpm version bump" to whatever your team treats as a dependency-review trigger.
  5. If you upgraded on purpose, keep 6.x as the source of truth for emit and tooling, and run TypeScript 7 as a second, non-blocking tsc job until 7.1 lands on 2026-11-10.
  6. If lint takes more than a couple of minutes in CI, benchmark tsgolint against your rule set this week and decide the two-rule gap knowingly rather than in an incident.

What I read for this piece: the pnpm 12.0.0 release notes, typescript-eslint issues #3622, #10940 and #12518, the TypeScript 7.1 iteration plan, the oxc type-aware linting announcement, and Rush's compatibility database page.

FAQ

Does typescript-eslint support TypeScript 7?
No. The support request, #12518, was closed as not planned on GA day, because TypeScript 7.0 shipped without a stable programmatic API. The 7.1 iteration plan targets API stabilization for 2026-11-10.

How do I stop pnpm from installing TypeScript 7 when my package.json pins 6.x?
Upgrade to pnpm 12.0.0, which removed the compatibility-database entry that gave @typescript-eslint/types an unpinned typescript dependency. Failing that, add an overrides entry pinning typescript to your 6.x line.

Is ignoreCompatibilityDb safe to turn on?
It resolves this specific break and disables every other bundled packageExtensions fixup at the same time, including ones unrelated packages rely on. Use it as a temporary unblock, then upgrade.

Can I lint with type information on TypeScript 7 today?
Yes, through oxc's tsgolint, stable since 22 July 2026. It links typescript-go directly, tracks v7.0.2, and covers 59 of the 61 type-aware rules.


Originally published at indragustiprasetya.com

Top comments (1)

Collapse
 
topstar_ai profile image
Luis Cruz

The insight about the pnpm compatibility database and its impact on version resolution is particularly valuable, especially in understanding how automatic dependency injections can lead to unexpected behaviors. One practical enhancement could be to document specific troubleshooting steps for users encountering this issue, as it can save significant debugging time. If you’re looking for assistance in refining error handling or improving documentation around these edge cases, I’d be glad to explore a paid collaboration. Have you considered any automated tools that could help preemptively catch these version-related issues?