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'd

View on GitHub (pinned to 35bff19146)

Solutions

  1. Narrow the input artifact's `path` to a specific file, not a directory that contains a mount point
  2. Move the nested volumeMount outside the artifact's path subtree (change mountPath)
  3. Reorder: don't declare the artifact path as an ancestor of any mountPath
  4. 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

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


AI-assisted analysis of argoproj/argo-workflows@35bff19146 (2026-09-03). Data as JSON: /api/errors/9c5d7164fdf8437a. Report an issue: GitHub.