{"record":{"id":"cafe8080112ea389","repo":"ipfs/kubo","slug":"too-many-levels-of-symbolic-links-s","errorCode":null,"errorMessage":"too many levels of symbolic links: %s","messagePattern":"too many levels of symbolic links: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"core/commands/keystore.go","lineNumber":412,"sourceCode":"\t\t}\n\t\ttarget, err := os.Readlink(path)\n\t\tif err != nil {\n\t\t\treturn \"\", err\n\t\t}\n\t\tif !filepath.IsAbs(target) {\n\t\t\t// A relative target starts at the directory the link lives in,\n\t\t\t// with that directory's own links resolved. Joining it onto the\n\t\t\t// path as typed would collapse \"..\" through them and name a file\n\t\t\t// the kernel never points at.\n\t\t\tdir, err := filepath.EvalSymlinks(filepath.Dir(path))\n\t\t\tif err != nil {\n\t\t\t\treturn \"\", err\n\t\t\t}\n\t\t\ttarget = filepath.Join(dir, target)\n\t\t}\n\t\tpath = target\n\t}\n\treturn \"\", fmt.Errorf(\"too many levels of symbolic links: %s\", path)\n}\n\n// writeExportedKeyInPlace writes to an existing character device or pipe. The\n// target type is confirmed on the open descriptor, so a path swapped for a\n// regular file after the stat cannot receive the key.\nfunc writeExportedKeyInPlace(outPath string, writeKey func(io.Writer) error) (err error) {\n\t// No O_TRUNC: devices and pipes ignore it, and a regular file that reached\n\t// this path through a race must not be emptied.\n\tfile, err := os.OpenFile(outPath, os.O_WRONLY, 0)\n\tif err != nil {\n\t\treturn err\n\t}\n\tdefer func() { err = errors.Join(err, file.Close()) }()\n\n\tinfo, err := file.Stat()\n\tif err != nil {\n\t\treturn err\n\t}","sourceCodeStart":394,"sourceCodeEnd":430,"githubUrl":"https://github.com/ipfs/kubo/blob/329838acdfafae224582930457efe80aa217afc0/core/commands/keystore.go#L394-L430","documentation":"Before the atomic write, resolveSymlink follows the output path's symlink chain manually (so a final dangling link still resolves). It follows at most maxSymlinkHops (8) links; if the chain is longer it refuses with this ELOOP-style error rather than looping forever. Nearly always this means a genuine symlink loop (a -> b -> a) or an absurdly deep chain.","triggerScenarios":"`ipfs key export -o <path>` where <path> contains a symlink cycle, e.g. `ln -s a b && ln -s b a` then exporting to `a`. Also theoretically chains deeper than 8 links.","commonSituations":"Accidental self-referential symlinks left by broken provisioning scripts or failed deploys; test fixtures that leaked loops into /tmp; symlinking the key output path to itself.","solutions":["Inspect the chain: `ls -la` each link or `readlink -f <path>` (which will also report the loop)","Delete and recreate the offending symlink to point at a real file/directory","Do not point the -o target at a link that (transitively) points back to itself","Note the tool never creates symlinks, so the loop was created externally and must be removed externally"],"exampleFix":"// before\nln -s b a && ln -s a b && ipfs key export mykey -o a\n// after\nrm a b && ipfs key export mykey -o a.pem","handlingStrategy":"validation","validationCode":"# reject symlink loops before export\nrealpath -e \"$out\" >/dev/null 2>&1 || echo 'path does not resolve (possible loop)'\n# count hops\np=\"$out\"; for i in $(seq 1 9); do l=$(readlink \"$p\" || break); p=\"$l\"; done; [ $i -lt 9 ] || echo 'too many symlink levels'","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Run `readlink -f <out>` to verify the path resolves before exporting","Clean up loops in /tmp and fixture directories immediately after tests","Avoid chained symlinks for key storage paths; link directly to the real file"],"tags":["cli","keystore","filesystem","symlink"],"backgroundTag":"symlink-loop","analyzedSha":"329838acdfafae224582930457efe80aa217afc0","analyzedAt":"2026-09-03T18:30:52.135Z","contentChangedAt":"2026-09-03T18:30:52.135Z","schemaVersion":2},"datasetVersion":"2026-09-11T00:17:11.886Z"}