argoproj/argo-workflows · error

os open: %w

Error message

os open: %w

What it means

This error is thrown by uploadObject when os.Open fails on the local file being uploaded to GCS. It wraps the standard library *PathError (ENOENT, EACCES, EISDIR, etc.) and indicates the artifact driver could not even start reading the local artifact file — no bytes were sent to GCS. It is reached from uploadObjects when saving a single file artifact, or per-file when walking a directory artifact.

Source

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

			if err != nil {
				return fmt.Errorf("upload %s: %w", dirName+relPath, err)
			}
		}
	} else {
		objectKey := normalizeGCSKey(filepath.Clean(key))
		err = uploadObject(ctx, client, bucket, objectKey, path)
		if err != nil {
			return fmt.Errorf("upload %s: %w", path, err)
		}
	}
	return nil
}

// upload an object to GCS
func uploadObject(ctx context.Context, client *storage.Client, bucket, key, localPath string) error {
	f, err := os.Open(filepath.Clean(localPath))
	if err != nil {
		return fmt.Errorf("os open: %w", err)
	}
	defer func() {
		if closeErr := f.Close(); closeErr != nil {
			logger := logging.RequireLoggerFromContext(ctx)
			logger.WithField("path", localPath).WithError(closeErr).Error(ctx, "Error closing file")
		}
	}()
	wc := client.Bucket(bucket).Object(key).NewWriter(ctx)
	if _, err = io.Copy(wc, f); err != nil {
		return fmt.Errorf("io copy: %w", err)
	}
	if err := wc.Close(); err != nil {
		return fmt.Errorf("writer close: %w", err)
	}
	return nil
}

// delete an object from GCS

View on GitHub (pinned to 35bff19146)

Solutions

  1. Run `ls -la` / `stat` on the exact localPath from the error message inside the pod to confirm existence and permissions.
  2. Fix the producing step so it writes the artifact file (check its exit code and logs) before artifact collection runs.
  3. Correct permissions (`chmod`/`chown`) or run the executor with sufficient rights to read the artifact volume.
  4. If uploading a directory, ensure nothing deletes/moves files concurrently during the workflow's artifact save phase.
  5. Validate the artifact path in the workflow spec is absolute and matches the mounted volume mountPath.

Example fix

// before: artifact declared but step never created it
- name: out
  path: /mnt/out/result.json
// after: guarantee the file exists in the script
script:
  command: [sh]
  source: |
    mkdir -p /mnt/out
    echo "{\"ok\": true}" > /mnt/out/result.json
Defensive patterns

Strategy: validation

Validate before calling

func assertReadable(path string) error {
    info, err := os.Stat(path)
    if err != nil {
        return fmt.Errorf("artifact missing: %w", err)
    }
    if info.IsDir() {
        return fmt.Errorf("%s is a directory; Save handles dirs separately", path)
    }
    f, err := os.Open(path)
    if err != nil {
        return fmt.Errorf("artifact unreadable: %w", err)
    }
    return f.Close()
}

Type guard

func isOSOpenErr(err error) (*fs.PathError, bool) {
    var perr *fs.PathError
    ok := errors.As(err, &perr) && strings.Contains(err.Error(), "os open:")
    return perr, ok
}

Try / catch

err := driver.Save(ctx, path, artifact)
if err != nil {
    var perr *fs.PathError
    if errors.As(err, &perr) {
        if errors.Is(perr, fs.ErrNotExist) {
            log.Printf("artifact %s was never produced", perr.Path)
        } else if errors.Is(perr, fs.ErrPermission) {
            log.Printf("fix permissions on %s", perr.Path)
        }
    }
}

Prevention

When it happens

Trigger: uploadObject(ctx, client, bucket, key, localPath) with a localPath that does not exist, is a directory (when the isDir detection was bypassed or raced), or lacks read permission. Also occurs when the path contains characters removed by filepath.Clean or the file was deleted after listFileRelPaths enumerated a directory artifact.

Common situations: Workflow step failed to write its output before artifact saving started; artifact path points to a mounted volume that was unmounted; running argoexec as non-root against root-owned files; symlink pointing to a missing target; TOCTOU race between directory listing and file open on a changing output directory.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


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