{"record":{"id":"3e6ef3e3898b75e6","repo":"can1357/oh-my-pi","slug":"too-many-levels-of-symbolic-links","errorCode":null,"errorMessage":"Too many levels of symbolic links","messagePattern":"Too many levels of symbolic links","errorType":"exception","errorClass":"CatError","httpStatus":null,"severity":"error","filePath":"crates/pi-builtins/src/cat.rs","lineNumber":83,"sourceCode":"}\n\n#[derive(Error, Debug)]\nenum CatError {\n\t/// Wrapper around `io::Error`.\n\t#[error(\"{}\", strip_errno(.0))]\n\tIo(io::Error),\n\t/// The downstream reader closed its pipe; this ends the copy quietly.\n\t#[error(\"broken pipe\")]\n\tBrokenPipe,\n\t/// Unknown file type; it is not a regular file, socket, or known device.\n\t#[error(\"unknown filetype: {ft_debug}\")]\n\tUnknownFiletype { ft_debug: String },\n\t#[error(\"Is a directory\")]\n\tIsDirectory,\n\t#[cfg(unix)]\n\t#[error(\"No such device or address\")]\n\tNoSuchDeviceOrAddress,\n\t#[error(\"Too many levels of symbolic links\")]\n\tTooManySymlinks,\n}\n\nimpl From<io::Error> for CatError {\n\tfn from(error: io::Error) -> Self {\n\t\tif error.kind() == ErrorKind::BrokenPipe {\n\t\t\tSelf::BrokenPipe\n\t\t} else {\n\t\t\tSelf::Io(error)\n\t\t}\n\t}\n}\n\nfn strip_errno(error: &io::Error) -> String {\n\tlet mut message = error.to_string();\n\tif let Some(position) = message.find(\" (os error \") {\n\t\tmessage.truncate(position);\n\t}","sourceCodeStart":65,"sourceCodeEnd":101,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/crates/pi-builtins/src/cat.rs#L65-L101","documentation":"This io::Error (ELOOP, 'Too many levels of symbolic links') is surfaced through CatError::TooManySymlinks in the cat builtin. The OS throws it when resolving a path requires following more symlinks than the kernel allows (typically 40), which almost always means a symlink loop. The builtin wraps OS open/read failures so callers get a typed, displayable error per file.","triggerScenarios":"Running the cat builtin on a path whose resolution traverses a symlink cycle, e.g. `ln -s loop loop; cat loop`, or a chain of symlinks pointing back at an ancestor (a -> b -> a).","commonSituations":"Broken symlink setups in dotfiles repos, build systems or package symlinks that accidentally self-reference, restoring from backups with `cp -a` misconfigurations, or symlinking a directory into itself.","solutions":["Inspect the path with `ls -la` or `readlink -f <path>` to find the symlink cycle and remove/repoint the offending link","Use `find -L <dir> -type l` to detect loops under a directory tree before batching cat over it","If the loop is intentional (rare), read the target file directly rather than through the symlink","Handle CatError::TooManySymlinks per-file so one bad link does not abort concatenating the remaining files"],"exampleFix":"// before\nln -s config config  # self-referential symlink\ncat config            # ELOOP\n// after\nrm config\nln -s real-config config\ncat config","handlingStrategy":"try-catch","validationCode":"import std::fs;import std::os::unix::fs::MetadataExt;fn has_symlink_loop(path: &str) -> bool { fs::metadata(path).is_err() && fs::symlink_metadata(path).is_ok() }","typeGuard":"fn is_eloop(err: &CatError) -> bool { matches!(err, CatError::TooManySymlinks) || err.to_string().contains(\"Too many levels of symbolic links\") }","tryCatchPattern":"match cat_file(path) { Err(CatError::TooManySymlinks) => eprintln!(\"skipping {}: symlink loop\", path), Err(e) => return Err(e), Ok(_) => {} }","preventionTips":["Run find -L <dir> -type l in CI to detect symlink cycles","Never symlink a path into its own ancestor without an escape condition","Use readlink -f to canonicalize paths before scripting against them"],"tags":["io","symlink","filesystem","rust"],"backgroundTag":"symlink-loop-eloop","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}