argoproj/argo-workflows · error

failed to create destination %s: %w

Error message

failed to create destination %s: %w

What it means

saveArtifact failed to create the artifact destination file (<dstPath> under /var/run/argo/outputs/artifacts) via os.Create after its parent directory was made. The step's output artifact cannot be written. The wrapped OS error carries the concrete cause.

Source

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

		logger.WithField("srcPath", srcPath).Info(ctx, "no need to save artifact - on overlapping volume")
		return nil
	}
	if _, err := os.Stat(srcPath); os.IsNotExist(err) { // might be optional, so we ignore
		logger.WithField("srcPath", srcPath).WithError(err).Warn(ctx, "cannot save artifact")
		return nil
	}
	dstPath := filepath.Join(varRunArgo, "/outputs/artifacts/", strings.TrimSuffix(srcPath, "/")+".tgz")
	logger.WithFields(logging.Fields{
		"src": srcPath,
		"dst": dstPath,
	}).Info(ctx, "saving artifact")
	z := filepath.Dir(dstPath)
	if err := os.MkdirAll(z, 0o755); err != nil { // chmod rwxr-xr-x
		return fmt.Errorf("failed to create directory %s: %w", z, err)
	}
	dst, err := os.Create(dstPath)
	if err != nil {
		return fmt.Errorf("failed to create destination %s: %w", dstPath, err)
	}
	defer func() { _ = dst.Close() }()
	if err = archive.TarGzToWriter(ctx, srcPath, gzip.DefaultCompression, dst); err != nil {
		return fmt.Errorf("failed to tarball the output %s to %s: %w", srcPath, dstPath, err)
	}
	if err = dst.Close(); err != nil {
		return fmt.Errorf("failed to close %s: %w", dstPath, err)
	}
	return nil
}

func saveParameter(ctx context.Context, template *wfv1.Template, srcPath string) error {
	logger := logging.RequireLoggerFromContext(ctx)

	if common.FindOverlappingVolume(template, srcPath) != nil {
		logger.WithField("src", srcPath).Info(ctx, "no need to save parameter - on overlapping volume")
		return nil
	}

View on GitHub (pinned to 35bff19146)

Solutions

  1. Check the wrapped error: EISDIR means the template's artifact path must point to a file, not a directory.
  2. Increase pod ulimits (RLIMIT_NOFILE) if EMFILE appears; reduce concurrent output saving.
  3. Free disk space if ENOSPC.
  4. Fix write permissions/UID for /var/run/argo/outputs (volume + securityContext).

Example fix

// before
outputs:
  artifacts:
  - name: dir-out
    path: /tmp/results      # directory -> os.Create fails EISDIR
// after: archive a file path (directories get tarballed from srcPath)
outputs:
  artifacts:
  - name: dir-out
    path: /tmp/results.tgz
Defensive patterns

Strategy: validation

Validate before calling

// ensure the artifact destination is a file path, not a directory path
if fi, err := os.Stat(dstPath); err == nil && fi.IsDir() {
    return fmt.Errorf("artifact destination %s is a directory", dstPath)
}
// check fd headroom
var lim syscall.Rlimit
_ = syscall.Getrlimit(syscall.RLIMIT_NOFILE, &lim)

Try / catch

err := runStep(ctx)
if err != nil && strings.Contains(err.Error(), "failed to create destination") {
    if errors.Is(errors.Unwrap(err), syscall.EISDIR) {
        return fixTemplateArtifactPath(ctx) // path must end in a file
    }
    return err
}

Prevention

When it happens

Trigger: os.Create(dstPath) fails: destination path is a directory, too many open files (EMFILE), disk full, or write permission denied on the newly created output directory.

Common situations: Artifact path in the template points at a directory instead of a file; hitting the pod's file-descriptor limit while many artifacts/parameters save concurrently; ENOSPC; SELinux/AppArmor denying writes.

Related errors


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