{"record":{"id":"add238d218dccc0e","repo":"parcel-bundler/parcel","slug":"notfound-add238","errorCode":"NotFound","errorMessage":"Circular symlink","messagePattern":"Circular symlink","errorType":"error_code","errorClass":"std::io::Error","httpStatus":null,"severity":"error","filePath":"crates/parcel-resolver/src/cache.rs","lineNumber":238,"sourceCode":"  }\n\n  /// Returns whether the path is a node_modules directory.\n  pub fn is_node_modules(&self) -> bool {\n    self.0.flags.contains(PathFlags::IS_NODE_MODULES)\n  }\n\n  /// Returns whether the path is inside a node_modules directory.\n  pub fn in_node_modules(&self) -> bool {\n    self.0.flags.contains(PathFlags::IN_NODE_MODULES)\n  }\n\n  /// Returns the canonical path, resolving all symbolic links.\n  pub fn canonicalize(&self, cache: &Cache) -> Result<CachedPath, ResolverError> {\n    // Check if this thread is already canonicalizing. If so, we have found a circular symlink.\n    // If a different thread is canonicalizing, OnceLock will queue this thread to wait for the result.\n    let tid = THREAD_ID.with(|t| *t);\n    if self.0.canonicalizing.load(Ordering::Acquire) == tid {\n      return Err(std::io::Error::new(std::io::ErrorKind::NotFound, \"Circular symlink\").into());\n    }\n\n    self\n      .0\n      .canonical\n      .get_or_init(|| {\n        self.0.canonicalizing.store(tid, Ordering::Release);\n\n        let res = self\n          .parent()\n          .map(|parent| {\n            parent.canonicalize(cache).and_then(|parent_canonical| {\n              let path = parent_canonical.join(\n                self\n                  .as_path()\n                  .strip_prefix(parent.as_path())\n                  .map_err(|_| ResolverError::UnknownError)?,\n                cache,","sourceCodeStart":220,"sourceCodeEnd":256,"githubUrl":"https://github.com/parcel-bundler/parcel/blob/59484858a1a0bcbb71f74088956bb437a2db6505/crates/parcel-resolver/src/cache.rs#L220-L256","documentation":"Thrown by CachedPath::canonicalize when the current thread is already in the middle of canonicalizing the same path. This is the cycle detector for symbolic links: each thread stamps a path with its thread id before recursing through parents/symlink targets, and re-encountering its own stamp means the symlink chain loops back on itself (a→b→a). The error is wrapped as std::io::ErrorKind::NotFound so the resolver treats the cycle as an unresolvable path.","triggerScenarios":"A filesystem contains a symlink cycle (symlink A points to B, B points to A, or A points to a descendant of itself) and the resolver attempts to canonicalize a path that traverses it. Also possible with self-referential symlinks (ln -s self self). Fires during any resolve call that walks through the cyclic symlink.","commonSituations":"Broken symlinks left over from a botched npm/pnpm install (especially pnpm's symlink-heavy node_modules layout); manually created symlinks that accidentally form a loop; a build tool or watcher that creates symlinks into its own output directory; monorepo workspace symlinks misconfigured to point upward.","solutions":["Find and remove the offending symlink cycle: run 'find <project_root> -type l' and inspect targets, or use 'symlinks -r <project_root>' / 'ls -lL' to expose loops.","Reinstall node_modules (rm -rf node_modules && npm install) to clear corrupted package symlinks.","If a symlink is intentional, ensure its target is outside the canonicalization chain (no ancestor/descendant relationship).","Wrap the resolve call in try/catch and report the path so the user can locate the cycle."],"exampleFix":"// before — resolver hits the cycle during canonicalization\nconst result = resolver.resolve({ filename, specifierType: 'esm', parent });\n// error surfaces indirectly as a resolution failure\n\n// after — detect and report the symlink cycle in your project\n// from a shell, find and break the loop:\n//   find . -type l -exec ls -l {} \\; | awk '$NF ~ /\\.$|loop/' \n// or reinstall to rebuild node_modules symlinks:\n//   rm -rf node_modules && npm install\n// then retry the resolve\ntry {\n  const result = resolver.resolve({ filename, specifierType: 'esm', parent });\n} catch (e) {\n  throw new Error(`Resolution failed for ${filename}; possible symlink cycle under ${parent}. Run: find ${parent} -type l -exec ls -l {} \\;`);\n}","handlingStrategy":"try-catch","validationCode":"import { readdirSync, lstatSync, readlinkSync } from 'node:fs';\nimport { join } from 'node:path';\n// Detect symlink cycles in a directory tree before resolving\nfunction findSymlinkCycles(root, seen = new Set()) {\n  for (const entry of readdirSync(root, { withFileTypes: true })) {\n    const full = join(root, entry.name);\n    if (entry.isSymbolicLink()) {\n      let target = full, hops = 0;\n      while (hops++ < 40) {\n        target = readlinkSync(target);\n        if (target === full || seen.has(target)) {\n          return full; // found a cycle\n        }\n        seen.add(target);\n      }\n    } else if (entry.isDirectory()) {\n      const found = findSymlinkCycles(full, seen);\n      if (found) return found;\n    }\n  }\n  return null;\n}","typeGuard":null,"tryCatchPattern":"try {\n  return resolver.resolve(opts);\n} catch (e) {\n  if (e.message === 'Circular symlink' || e.code === 'NotFound') {\n    throw new Error(`Symlink cycle detected under ${opts.parent}. Run: find ${opts.parent} -type l -exec ls -l {} \\;`);\n  }\n  throw e;\n}","preventionTips":["After install issues, rm -rf node_modules and reinstall to clear broken package symlinks (pnpm is especially symlink-heavy).","Avoid creating symlinks that point into their own ancestor or descendant directories.","Run 'find . -type l' periodically in projects with heavy symlinking to catch loops early.","In CI, add a check that fails if the resolver reports NotFound on a known-good entry — it often masks a cycle."],"tags":["resolver","symlink","filesystem","cycle","not-found"],"backgroundTag":null,"analyzedSha":"59484858a1a0bcbb71f74088956bb437a2db6505","analyzedAt":"2026-08-13T04:06:35.925Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}