tailwindlabs/tailwindcss · error · Error

Exceeded maximum recursion depth while resolving `${uri}` in

Error message

Exceeded maximum recursion depth while resolving `${uri}` in `${base}`)

What it means

Thrown by `substituteAtImports` when `recurseCount > 100`. Tailwind does not fully resolve import paths, so it cannot reliably detect true cycles; instead it caps `@import` recursion depth. Hitting the cap almost always indicates a circular import chain (A imports B imports A).

Source

Thrown at packages/tailwindcss/src/at-import.ts:54

      let { uri, layer, media, supports } = parsed

      // Skip importing data or remote URIs
      if (uri.startsWith('data:')) return
      if (uri.startsWith('http://') || uri.startsWith('https://')) return

      let contextNode = context({}, [])

      promises.push(
        (async () => {
          // Since we do not have fully resolved paths in core, we can't
          // reliably detect circular imports. Instead, we try to limit the
          // recursion depth to a number that is too large to be reached in
          // practice.
          if (recurseCount > 100) {
            throw new Error(
              `Exceeded maximum recursion depth while resolving \`${uri}\` in \`${base}\`)`,
            )
          }

          let loaded = await loadStylesheet(uri, base)
          let ast = CSS.parse(loaded.content, { from: track ? loaded.path : undefined })
          await substituteAtImports(ast, loaded.base, loadStylesheet, recurseCount + 1, track)

          contextNode.nodes = buildImportNodes(
            node,
            [context({ base: loaded.base }, ast)],
            layer,
            media,
            supports,
          )
        })(),
      )

      // The resolved Stylesheets already have their transitive @imports
      // resolved, so we can skip walking them.
      return WalkAction.ReplaceSkip(contextNode)

View on GitHub (pinned to 16e94cbf7f)

Solutions

  1. Inspect the `uri` and `base` in the message and trace the import chain back to the file that re-imports an ancestor.
  2. Break the cycle by inlining the shared CSS into a third file both import once.
  3. Remove redundant `@import` lines introduced during a merge or refactor.

Example fix

// before
// a.css
@import "b.css";
// b.css
@import "a.css"; /* circular */
// after
// shared.css holds the common rules; a.css and b.css both import only shared.css
Defensive patterns

Strategy: validation

Validate before calling

function detectImportCycle(entry, load, seen = new Set()) {
  if (seen.has(entry)) throw new Error(`Circular @import involving ${entry}`);
  seen.add(entry);
  for (const imp of extractImports(load(entry))) detectImportCycle(imp, load, new Set(seen));
}

Prevention

When it happens

Trigger: Two stylesheets that `@import` each other, or a self-importing file; a long legitimate chain deeper than 100 (extremely rare). The guard fires before attempting to `loadStylesheet` for the current `uri`.

Common situations: Refactor that moved `@import` statements between files and accidentally created a cycle; generated CSS that emits imports programmatically; broken relative paths that resolve back to the parent.

Related errors


AI-assisted analysis of tailwindlabs/tailwindcss@16e94cbf7f (2026-08-12). Data as JSON: /api/errors/fab287ce23e5114e. Report an issue: GitHub.