{"record":{"id":"2f27e63f30a46fb9","repo":"denoland/deno","slug":"circular-symlink-detected","errorCode":null,"errorMessage":"Circular symlink detected: {} -> {}","messagePattern":"Circular symlink detected: (.+?) -> (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cli/lib/standalone/virtual_fs.rs","lineNumber":1187,"sourceCode":"        VfsEntry::Symlink(VirtualSymlink {\n          name: name.to_string(),\n          dest_parts: VirtualSymlinkParts::from_path(target.as_ref()),\n          dest_is_dir,\n        })\n      },\n      |_| {\n        // ignore previously inserted\n      },\n    );\n    #[allow(clippy::disallowed_methods, reason = \"ok, creating vfs\")]\n    let target_metadata = std::fs::symlink_metadata(target.as_ref())\n      .with_context(|| {\n        format!(\"Reading symlink target '{}'\", target.display())\n      })?;\n    if target_metadata.is_symlink() {\n      if !visited.insert(target.as_ref().to_path_buf()) {\n        // todo: probably don't error in this scenario\n        bail!(\n          \"Circular symlink detected: {} -> {}\",\n          visited\n            .iter()\n            .map(|p| p.display().to_string())\n            .collect::<Vec<_>>()\n            .join(\" -> \"),\n          target.display()\n        );\n      }\n      self.add_symlink_inner(&target, visited)\n    } else if target_metadata.is_dir() {\n      Ok(SymlinkTarget::Dir(target.0.into_owned()))\n    } else {\n      Ok(SymlinkTarget::File(target.0.into_owned()))\n    }\n  }\n\n  /// Adds the CJS export analysis to the provided file.","sourceCodeStart":1169,"sourceCodeEnd":1205,"githubUrl":"https://github.com/denoland/deno/blob/9ad36f7a2cce60488e6ec52283efb32efddaf93a/cli/lib/standalone/virtual_fs.rs#L1169-L1205","documentation":"When deno compile builds the virtual filesystem for a standalone binary, it follows directory symlinks while tracking visited paths in a set. Revisiting a path means a symlink cycle; the VFS builder bails with the chain of visited targets plus the offending link, since packing a cycle would never terminate.","triggerScenarios":"deno compile over a tree where symlinked directories form a loop - a -> b and b -> a, or a link pointing at an ancestor directory that is itself being packed.","commonSituations":"Monorepos with circular convenience links; alias directories created by tooling; manual links (ln -s ..) inside directories that get embedded.","solutions":["Break the cycle: replace one side of the loop with a real directory or file","Point symlinks at concrete targets outside the packed tree, or remove them","Exclude the cyclical directory from the embedded set and ship those assets alongside the binary instead"],"exampleFix":"# before\nln -s ../b a\nln -s ../a b   # compile fails: Circular symlink detected: a -> b -> a\n\n# after\n# make one side a real directory\ncp -RL a a.real && rm a && mv a.real a","handlingStrategy":"validation","validationCode":"# Detect symlink cycles before deno compile\nfind . -type l -exec realpath -e {} \\; 2>/dev/null | sort | uniq -d | while read -r t; do\n  echo \"multiple links resolve to $t - check for a cycle\"\ndone\n# or list directory symlinks that point back into the tree\nfind . -type l | while read -r l; do\n  case \"$(readlink \"$l\")\" in \n    .*|/*\"$PWD\"*) echo \"suspicious link: $l -> $(readlink \"$l\")\";;\n  esac\ndone","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Avoid directory symlinks inside trees that deno compile embeds; prefer real directories or file copies","Run a cycle check over symlinks in the build script before invoking deno compile","Read the chain in the error message - it shows the exact loop that must be broken"],"tags":["deno-compile","symlink","virtual-fs","packaging"],"backgroundTag":"symlink-loop-detected","analyzedSha":"9ad36f7a2cce60488e6ec52283efb32efddaf93a","analyzedAt":"2026-08-20T13:07:44.778Z","contentChangedAt":"2026-08-20T13:07:44.778Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}