argoproj/argo-workflows · error
failed to resolve parent of artifact path %q at %s: %w
Error message
failed to resolve parent of artifact path %q at %s: %w
What it means
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.
Source
Thrown at cmd/argoexec/commands/emissary.go:424
// for it). Only an *overwrite* can destroy data, and that is gated below.
if _, err := os.Lstat(dst); err != nil {
if !os.IsNotExist(err) {
return fmt.Errorf("failed to stat artifact path %q at %s: %w", art.Name, dst, err)
}
} else {
// Something is already at art.Path. Replacing it (os.RemoveAll then
// symlink) reproduces the legacy SubPath mount's shadowing — but only
// when it is safe. RemoveAll resolves symlinks in the parent chain, so
// resolve the parent to find where the delete would actually land
// (resolve the parent, not the final element, so an image symlink *at*
// art.Path is just unlinked rather than followed). If that resolved
// path overlaps a user-declared volume, clearing it would recurse into
// and destroy a live PVC/hostPath/emptyDir, so refuse. Benign system
// mounts (tmpfs /run, the overlay rootfs) are not declared user volumes
// and so remain safe to shadow.
realParent, evalErr := filepath.EvalSymlinks(filepath.Dir(dst))
if evalErr != nil {
return fmt.Errorf("failed to resolve parent of artifact path %q at %s: %w", art.Name, dst, evalErr)
}
resolved := filepath.Join(realParent, filepath.Base(dst))
if mnt := common.FindOverlappingVolume(tmpl, resolved); mnt != nil {
return 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)
}
if mnt := common.FindVolumeMountNestedUnderPath(tmpl, resolved); mnt != nil {
return 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)
}
if rmErr := os.RemoveAll(dst); rmErr != nil {
return fmt.Errorf("failed to clear existing path for artifact %q at %s: %w", art.Name, dst, rmErr)
}
}
if err := os.Symlink(src, dst); err != nil {
return fmt.Errorf("failed to symlink input artifact %q (%s -> %s): %w", art.Name, dst, src, err)
}
logger.WithFields(logging.Fields{"name": art.Name, "src": src, "dst": dst}).Debug(ctx, "linked input artifact")
}
return nilView on GitHub (pinned to 35bff19146)
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
Example fix
# before
inputs:
artifacts:
- name: cfg
path: /etc/alternatives/mycfg/my.yaml # /etc/alternatives/mycfg is a dangling symlink in the image
# after
inputs:
artifacts:
- name: cfg
path: /etc/myapp/my.yaml Defensive patterns
Strategy: validation
Validate before calling
// In the container image, verify every component of the artifact path resolves: // namei -l /etc/myapp/my.yaml — no 'dangling' entries allowed. # shell check in an init container or image build: for p in /etc/myapp/my.yaml; do namei -l "$p" | grep -q dangling && echo "fix symlink chain for $p" && exit 1 done
Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- failed to stat artifact path %q at %s: %w
- failed to stat input artifact %q at %s: %w
- failed to create parent directory for artifact %q at %s: %w
- failed to clear existing path for artifact %q at %s: %w
- failed to symlink input artifact %q (%s -> %s): %w
AI-assisted analysis of argoproj/argo-workflows@35bff19146 (2026-09-03).
Data as JSON: /api/errors/1bc3bce9f90a807f.
Report an issue: GitHub.