{"record":{"id":"1bc3bce9f90a807f","repo":"argoproj/argo-workflows","slug":"failed-to-resolve-parent-of-artifact-path-q-at-s","errorCode":null,"errorMessage":"failed to resolve parent of artifact path %q at %s: %w","messagePattern":"failed to resolve parent of artifact path %q at (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"cmd/argoexec/commands/emissary.go","lineNumber":424,"sourceCode":"\t\t// for it). Only an *overwrite* can destroy data, and that is gated below.\n\t\tif _, err := os.Lstat(dst); err != nil {\n\t\t\tif !os.IsNotExist(err) {\n\t\t\t\treturn fmt.Errorf(\"failed to stat artifact path %q at %s: %w\", art.Name, dst, err)\n\t\t\t}\n\t\t} else {\n\t\t\t// Something is already at art.Path. Replacing it (os.RemoveAll then\n\t\t\t// symlink) reproduces the legacy SubPath mount's shadowing — but only\n\t\t\t// when it is safe. RemoveAll resolves symlinks in the parent chain, so\n\t\t\t// resolve the parent to find where the delete would actually land\n\t\t\t// (resolve the parent, not the final element, so an image symlink *at*\n\t\t\t// art.Path is just unlinked rather than followed). If that resolved\n\t\t\t// path overlaps a user-declared volume, clearing it would recurse into\n\t\t\t// and destroy a live PVC/hostPath/emptyDir, so refuse. Benign system\n\t\t\t// mounts (tmpfs /run, the overlay rootfs) are not declared user volumes\n\t\t\t// and so remain safe to shadow.\n\t\t\trealParent, evalErr := filepath.EvalSymlinks(filepath.Dir(dst))\n\t\t\tif evalErr != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to resolve parent of artifact path %q at %s: %w\", art.Name, dst, evalErr)\n\t\t\t}\n\t\t\tresolved := filepath.Join(realParent, filepath.Base(dst))\n\t\t\tif mnt := common.FindOverlappingVolume(tmpl, resolved); mnt != nil {\n\t\t\t\treturn fmt.Errorf(\"refusing to stage input artifact %q at %s: it resolves to %s inside volume mount %q (%s), and clearing it would destroy the mounted volume; change the artifact path or volume mount so they do not overlap\", art.Name, dst, resolved, mnt.Name, mnt.MountPath)\n\t\t\t}\n\t\t\tif mnt := common.FindVolumeMountNestedUnderPath(tmpl, resolved); mnt != nil {\n\t\t\t\treturn fmt.Errorf(\"refusing to stage input artifact %q at %s: it resolves to %s which contains volume mount %q (%s), and clearing it would destroy the mounted volume; change the artifact path or volume mount so they do not overlap\", art.Name, dst, resolved, mnt.Name, mnt.MountPath)\n\t\t\t}\n\t\t\tif rmErr := os.RemoveAll(dst); rmErr != nil {\n\t\t\t\treturn fmt.Errorf(\"failed to clear existing path for artifact %q at %s: %w\", art.Name, dst, rmErr)\n\t\t\t}\n\t\t}\n\t\tif err := os.Symlink(src, dst); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to symlink input artifact %q (%s -> %s): %w\", art.Name, dst, src, err)\n\t\t}\n\t\tlogger.WithFields(logging.Fields{\"name\": art.Name, \"src\": src, \"dst\": dst}).Debug(ctx, \"linked input artifact\")\n\t}\n\treturn nil","sourceCodeStart":406,"sourceCodeEnd":442,"githubUrl":"https://github.com/argoproj/argo-workflows/blob/35bff19146f5a6ada77468c431f2624bd577e373/cmd/argoexec/commands/emissary.go#L406-L442","documentation":"argoexec's emissary links input artifacts into the container by symlinking them at art.Path. Before overwriting an existing path it resolves the parent directory's symlinks (filepath.EvalSymlinks) to find where a delete would actually land. If the parent cannot be resolved — typically a dangling symlink or missing directory component in the path chain — this wrapped error is returned and the workflow step fails.","triggerScenarios":"An input artifact's `path` points under a directory chain containing a broken/dangling symlink or a directory that disappeared between the earlier Lstat and the EvalSymlinks call, during linkInputArtifactsAt while staging inputs.","commonSituations":"Typo'd artifact paths referencing image-provided symlinks; mount layout changed between image build and runtime; artifacts declared under /var/run symlinks that got clobbered; race where another init step removed a parent directory.","solutions":["Check the artifact `path` for symlink components that exist in the container image and point to non-existent targets; use a real directory path","Log into the failing pod's image (docker run) and run `namei -l <path>` or `ls -la` on each path component to find the dangling link","Move the artifact destination to a plain directory (e.g. /tmp or a dedicated emptyDir mount) instead of a symlinked system path","Ensure init containers or other steps are not deleting parent directories of artifact paths concurrently"],"exampleFix":"# before\ninputs:\n  artifacts:\n  - name: cfg\n    path: /etc/alternatives/mycfg/my.yaml   # /etc/alternatives/mycfg is a dangling symlink in the image\n# after\ninputs:\n  artifacts:\n  - name: cfg\n    path: /etc/myapp/my.yaml","handlingStrategy":"validation","validationCode":"// In the container image, verify every component of the artifact path resolves:\n// namei -l /etc/myapp/my.yaml  — no 'dangling' entries allowed.\n# shell check in an init container or image build:\nfor p in /etc/myapp/my.yaml; do\n  namei -l \"$p\" | grep -q dangling && echo \"fix symlink chain for $p\" && exit 1\n\ndone","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Avoid artifact paths under symlink-heavy system directories (/etc/alternatives, /proc, /sys)","Use plain directories (or dedicated emptyDir mounts) as artifact destinations","Test the workflow image once with `docker run --rm <img> namei -l <path>` before production","Keep init containers from mutating shared parent directories of artifact paths"],"tags":["kubernetes","argo-workflows","artifacts","symlink","filesystem"],"backgroundTag":"dangling-symlink-in-path","analyzedSha":"35bff19146f5a6ada77468c431f2624bd577e373","analyzedAt":"2026-09-03T19:34:35.908Z","contentChangedAt":"2026-09-03T19:34:35.908Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}