{"record":{"id":"08d408d4d5907fae","repo":"nektos/act","slug":"max-depth-d-of-symlinks-exceeded-while-reading-s","errorCode":null,"errorMessage":"max depth %d of symlinks exceeded while reading %s","messagePattern":"max depth (.+?) of symlinks exceeded while reading (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/runner/step_action_local.go","lineNumber":72,"sourceCode":"\t\t\t\t\t\treturn nil, nil, fs.ErrNotExist\n\t\t\t\t\t}\n\t\t\t\t\ttreader := tar.NewReader(tars)\n\t\t\t\t\theader, err := treader.Next()\n\t\t\t\t\tif errors.Is(err, io.EOF) {\n\t\t\t\t\t\treturn nil, nil, os.ErrNotExist\n\t\t\t\t\t} else if err != nil {\n\t\t\t\t\t\treturn nil, nil, err\n\t\t\t\t\t}\n\t\t\t\t\tif header.FileInfo().Mode()&os.ModeSymlink == os.ModeSymlink {\n\t\t\t\t\t\tspath, err = symlinkJoin(spath, header.Linkname, cpath)\n\t\t\t\t\t\tif err != nil {\n\t\t\t\t\t\t\treturn nil, nil, err\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn treader, tars, nil\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn nil, nil, fmt.Errorf(\"max depth %d of symlinks exceeded while reading %s\", maxSymlinkDepth, spath)\n\t\t\t}\n\t\t}\n\n\t\tactionModel, err := sal.readAction(ctx, sal.Step, actionDir, \"\", localReader(ctx), os.WriteFile)\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\tsal.action = actionModel\n\n\t\treturn sal.runAction(sal, actionDir, nil)(ctx)\n\t})\n}\n\nfunc (sal *stepActionLocal) post() common.Executor {\n\treturn runStepExecutor(sal, stepStagePost, runPostStep(sal)).If(hasPostStep(sal)).If(shouldRunPostStep(sal))\n}\n","sourceCodeStart":54,"sourceCodeEnd":90,"githubUrl":"https://github.com/nektos/act/blob/4f411281417e88660bea1c1a1749aa71ae0bd60f/pkg/runner/step_action_local.go#L54-L90","documentation":"While reading a local action (./path) through its tar stream, act followed symlinks up to maxSylinkDepth times without ever reaching a regular file: after maxSymlinkDepth iterations the loop exits and this error reports the last path examined. It guards against symlink cycles (a -> b -> a) or chains longer than the configured depth.","triggerScenarios":"A local action directory contains a symlink loop (two symlinks pointing at each other) or a chain of symlinks longer than maxSymlinkDepth for a file act tries to read (typically action.yml / action.yaml resolution).","commonSituations":"Circular symlinks created accidentally in .github/actions/*; broken symlink chains on case-insensitive filesystems; deep symlink chains from monorepo tooling setups.","solutions":["Run `find .github/actions/<name> -type l -exec ls -l {} \\;` and inspect every symlink for cycles or overly long chains.","Replace the chain with a single symlink or a real file.","Remove broken symlinks pointing at deleted files."],"exampleFix":"# before\n.github/actions/tool/action.yml -> alias.yml\n.github/actions/tool/alias.yml  -> action.yml   # cycle\n# after\n.github/actions/tool/action.yml  # real file","handlingStrategy":"validation","validationCode":"# detect symlink cycles / overlong chains in a local action dir\nfind .github/actions -type l -follow -lname '*cycle*' >/dev/null; python3 - <<'EOF'\nimport os\nfor root,_,files in os.walk('.github/actions'):\n    for f in files:\n        p=os.path.join(root,f)\n        if os.path.islink(p):\n            seen=set(); cur=p\n            while os.path.islink(cur) and cur not in seen:\n                seen.add(cur); cur=os.path.join(os.path.dirname(cur), os.readlink(cur))\n            if cur in seen and os.path.islink(cur):\n                print('cycle at', p)\nEOF","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Never create mutually-referencing symlinks in action dirs.","Prefer real files over symlinks inside actions.","Keep symlink chains under the documented depth."],"tags":["symlink","local-action","filesystem","security"],"backgroundTag":null,"analyzedSha":"4f411281417e88660bea1c1a1749aa71ae0bd60f","analyzedAt":"2026-08-15T09:19:46.307Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}