argoproj/argo-workflows · error

os create %s: %w

Error message

os create %s: %w

What it means

After successfully opening the GCS reader, downloadObject creates the local file with os.Create(localPath). This error wraps an os.Create failure, i.e. the local artifact file could not be opened for writing even though its directory exists.

Source

Thrown at workflow/artifacts/gcs/gcs.go:176

	relObjPath := strings.TrimPrefix(objName, objPrefix)
	localPath := filepath.Join(path, relObjPath)
	objectDir, _ := filepath.Split(localPath)
	if objectDir != "" {
		if err := os.MkdirAll(objectDir, 0o700); err != nil {
			return fmt.Errorf("mkdir %s: %w", objectDir, err)
		}
	}
	rc, err := client.Bucket(bucket).Object(objName).NewReader(ctx)
	if err != nil {
		if errors.Is(err, storage.ErrObjectNotExist) {
			return argoerrors.New(argoerrors.CodeNotFound, err.Error())
		}
		return fmt.Errorf("new bucket reader: %w", err)
	}
	defer rc.Close()
	out, err := os.Create(localPath)
	if err != nil {
		return fmt.Errorf("os create %s: %w", localPath, err)
	}
	defer func() {
		if closeErr := out.Close(); closeErr != nil {
			logger := logging.RequireLoggerFromContext(ctx)
			logger.WithField("path", localPath).WithError(closeErr).Error(ctx, "Error closing file")
		}
	}()
	_, err = io.Copy(out, rc)
	if err != nil {
		return fmt.Errorf("io copy: %w", err)
	}
	return nil
}

// list all the object names of the prefix in the bucket
func listByPrefix(ctx context.Context, client *storage.Client, bucket, prefix, delim string) ([]string, error) {
	ctx, cancel := context.WithTimeout(ctx, time.Second*30)
	defer cancel()

View on GitHub (pinned to 35bff19146)

Solutions

  1. Ensure the local path doesn't clash with an existing directory (check the GCS key names for trailing-slash objects)
  2. Grant the container user write access: securityContext.runAsUser + fsGroup matching the volume, or chmod the mount
  3. Shorten the artifact path or the GCS key names
  4. Check volume/filesystem health

Example fix

// before
securityContext: {runAsUser: 1000}  # volume owned by root, mode 755
// after
securityContext: {runAsUser: 1000, fsGroup: 1000}  # or add initContainer chmod on emptyDir
Defensive patterns

Strategy: validation

Validate before calling

import "os"
func canCreate(path string) error {
	if fi, err := os.Lstat(path); err == nil && fi.IsDir() {
		return fmt.Errorf("%s is a directory", path)
	}
	f, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, 0o600)
	if err != nil { return err }
	return f.Close()
}

Try / catch

if err := download(ctx, art, path); err != nil {
	var pe *fs.PathError
	if errors.As(err, &pe) && os.IsPermission(pe) {
		// fix runAsUser/fsGroup or chmod the volume before retry
	}
	return err
}

Prevention

When it happens

Trigger: os.Create fails on localPath: path exists as a directory, permission denied for the container user, path too long (ENAMETOOLONG), or filesystem/I/O error on the output volume.

Common situations: GCS object whose name ends with '/' produced a directory at localPath; running as non-root (runAsUser) without write access to /tmp or the output volume; artifact path exceeding filesystem name limits.

Related errors


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