argoproj/argo-workflows · error
refusing to stage input artifact %q at %s: it resolves to %s
Error message
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
What it means
A companion guard to the overlap check: when staging an input artifact that must overwrite an existing path, argoexec refuses if the resolved path *contains* a nested volume mount — deleting the path would recurse into and destroy a mounted volume declared lower in the tree. The executor fails fast to protect the volume's data.
Source
Thrown at cmd/argoexec/commands/emissary.go:431
// 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 nil
}
// waitForSupervisorReady blocks until the supervisor's status marker reports a
// terminal outcome (READY/FAILED), or until the supervisor is presumed dead.
// Used only in init-less pod mode where main and supervisor start concurrently.
// VarRunArgoPath itself is guaranteed to exist because the emissary has
// already created /var/run/argo/ctr/<name> earlier in main, which MkdirAll'dView on GitHub (pinned to 35bff19146)
Solutions
- Narrow the input artifact's `path` to a specific file, not a directory that contains a mount point
- Move the nested volumeMount outside the artifact's path subtree (change mountPath)
- Reorder: don't declare the artifact path as an ancestor of any mountPath
- Read the resolved path in the error message and verify with `kubectl describe pod` which mountPath falls under it
Example fix
# before
inputs:
artifacts:
- name: bundle
path: /work # /work/cache is a mounted PVC
volumeMounts:
- name: cache
mountPath: /work/cache
# after
inputs:
artifacts:
- name: bundle
path: /tmp/bundle
volumeMounts:
- name: cache
mountPath: /work/cache Defensive patterns
Strategy: validation
Validate before calling
// Ensure no volumeMount's mountPath is nested under the artifact destination path:
const containsMount = (artPath, mounts) =>
mounts.some(m => m.mountPath === artPath || m.mountPath.startsWith(artPath === '/' ? '/' : artPath + '/'));
if (containsMount(art.path, template.volumeMounts || []))
throw new Error(`artifact path ${art.path} contains a mountPath; narrow the path`); Prevention
- Point input artifacts at specific files, never at directory roots that host mounts
- Audit templates with both artifacts and volumeMounts for ancestor/descendant path relationships
- Keep mountPoints shallow and artifact paths deep and specific
- Run `argo lint` plus a local path-overlap check in CI for workflow templates
When it happens
Trigger: art.Path (after parent symlink resolution) is a parent directory of a declared volumeMount, the path already exists, and the staging code would RemoveAll it.
Common situations: Declaring an input artifact at /data while another mount is at /data/cache; artifact path at a directory root that a subPath volumeMount hangs off; broad artifact paths like / used as destinations.
Related errors
- refusing to stage input artifact %q at %s: it resolves to %s
- failed to re-enter working directory %q after staging input
- failed to stat input artifact %q at %s: %w
- failed to create parent directory for artifact %q at %s: %w
- failed to stat artifact path %q at %s: %w
AI-assisted analysis of argoproj/argo-workflows@35bff19146 (2026-09-03).
Data as JSON: /api/errors/9c5d7164fdf8437a.
Report an issue: GitHub.