can1357/oh-my-pi · error · CatError
Too many levels of symbolic links
Error message
Too many levels of symbolic links
What it means
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.
Source
Thrown at crates/pi-builtins/src/cat.rs:83
}
#[derive(Error, Debug)]
enum CatError {
/// Wrapper around `io::Error`.
#[error("{}", strip_errno(.0))]
Io(io::Error),
/// The downstream reader closed its pipe; this ends the copy quietly.
#[error("broken pipe")]
BrokenPipe,
/// Unknown file type; it is not a regular file, socket, or known device.
#[error("unknown filetype: {ft_debug}")]
UnknownFiletype { ft_debug: String },
#[error("Is a directory")]
IsDirectory,
#[cfg(unix)]
#[error("No such device or address")]
NoSuchDeviceOrAddress,
#[error("Too many levels of symbolic links")]
TooManySymlinks,
}
impl From<io::Error> for CatError {
fn from(error: io::Error) -> Self {
if error.kind() == ErrorKind::BrokenPipe {
Self::BrokenPipe
} else {
Self::Io(error)
}
}
}
fn strip_errno(error: &io::Error) -> String {
let mut message = error.to_string();
if let Some(position) = message.find(" (os error ") {
message.truncate(position);
}View on GitHub (pinned to 9690622007)
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
Example fix
// before ln -s config config # self-referential symlink cat config # ELOOP // after rm config ln -s real-config config cat config
Defensive patterns
Strategy: try-catch
Validate before calling
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() } Type guard
fn is_eloop(err: &CatError) -> bool { matches!(err, CatError::TooManySymlinks) || err.to_string().contains("Too many levels of symbolic links") } Try / catch
match cat_file(path) { Err(CatError::TooManySymlinks) => eprintln!("skipping {}: symlink loop", path), Err(e) => return Err(e), Ok(_) => {} } Prevention
- 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
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- 2
- {}: {error}
- failed to create a unique fc temporary file
- Permission denied
- dates before 1970 are unsupported
AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31).
Data as JSON: /api/errors/3e6ef3e3898b75e6.
Report an issue: GitHub.