argoproj/argo-workflows · error

failed to create parent directory for artifact %q at %s: %w

Error message

failed to create parent directory for artifact %q at %s: %w

What it means

Before creating an artifact symlink, the executor creates the destination's parent directory with os.MkdirAll(parent, 0o755). Failure here is fatal and wrapped with the artifact name and destination path. It typically means the destination resolves somewhere the executor cannot write — most often a user volume mounted with restrictive permissions, or a read-only filesystem.

Source

Thrown at cmd/argoexec/commands/emissary.go:399

// its user volume.
func linkInputArtifactsAt(ctx context.Context, baseDir string, tmpl *wfv1.Template) error {
	logger := logging.RequireLoggerFromContext(ctx)
	for _, art := range tmpl.Inputs.Artifacts {
		src := filepath.Join(baseDir, art.Name)
		if _, statErr := os.Lstat(src); statErr != nil {
			if os.IsNotExist(statErr) {
				logger.WithFields(logging.Fields{"name": art.Name, "path": art.Path}).Info(ctx, "no input-artifacts entry (optional or overlap) — skipping symlink")
				continue
			}
			return fmt.Errorf("failed to stat input artifact %q at %s: %w", art.Name, src, statErr)
		}
		dst := art.Path
		if dst == "" {
			continue
		}
		if parent := filepath.Dir(dst); parent != "" && parent != "/" {
			if err := os.MkdirAll(parent, 0o755); err != nil {
				return fmt.Errorf("failed to create parent directory for artifact %q at %s: %w", art.Name, dst, err)
			}
		}
		// If nothing exists at art.Path, just create the symlink. Creating is
		// always safe — os.Symlink returns EEXIST rather than overwriting and the
		// MkdirAll above only ever creates — so even when art.Path resolves into a
		// user volume we deliberately let the artifact land there (the user asked
		// 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

View on GitHub (pinned to 35bff19146)

Solutions

  1. Read the wrapped errno: EACCES/EPERM → permissions; EROFS → read-only mount; ENOTDIR → a path component is a file.
  2. For permission failures, set pod securityContext fsGroup/runAsUser or chmod the volume so the executor user can write.
  3. Remove readOnly from the volumeMount that receives the artifact.
  4. Correct artifact.path so it doesn't run through an existing file.
  5. Verify the destination volume is mounted (kubectl describe pod → Mounts).

Example fix

// before
securityContext:
  runAsUser: 1000   # cannot write to root-owned volume
// after
securityContext:
  runAsUser: 1000
  fsGroup: 1000     # volume group-writable
Defensive patterns

Strategy: validation

Validate before calling

// Pre-flight: can the executor user create dirs at the artifact parent?
parent=$(dirname '<artifact.path>'); test -w "$parent" || { echo "cannot write $parent" >&2; exit 1; }

Try / catch

try {
  await stageArtifacts(tmpl)
} catch (e) {
  if (String(e).includes('failed to create parent directory')) {
    // fix fsGroup/runAsUser or remove readOnly from the destination mount
  }
}

Prevention

When it happens

Trigger: os.MkdirAll fails for the artifact's destination parent: path lives on a read-only volume, the volume's fsGroup/permissions exclude the executor user (usually root), the path is invalid (e.g. a file exists where a directory component is expected), or the mount was unmounted.

Common situations: Artifacts writing into PVCs or emptyDir mounts with non-root securityContext and no fsGroup; subPath mounts pointing at files; hostPath volumes mounted read-only; artifact.path typos like /etc/passwd/foo making a path component a file.

Understand the failure class

Background: mkdir permission denied (EACCES): failed to create directory errors explained — this error's family across 32 libraries.

Related errors


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