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 GCSView on GitHub (pinned to 35bff19146)
Solutions
- Run `ls -la` / `stat` on the exact localPath from the error message inside the pod to confirm existence and permissions.
- Fix the producing step so it writes the artifact file (check its exit code and logs) before artifact collection runs.
- Correct permissions (`chmod`/`chown`) or run the executor with sufficient rights to read the artifact volume.
- If uploading a directory, ensure nothing deletes/moves files concurrently during the workflow's artifact save phase.
- 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
- Declare output artifacts only for files the step is guaranteed to write (use mkdir -p and explicit writes).
- Run the executor with a user that can read the artifact volume; avoid root-only output files.
- Never delete or rename files in the output directory while the workflow is still saving artifacts.
- Verify symlinks in artifact dirs resolve to existing targets.
- Test artifact paths locally with os.Stat before submitting the workflow.
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
- GCS client CredentialsFromJSONWithType: %w
- GCS storage.NewClient with credential: %w
- GCS storage.NewClient: %w
- mkdir %s: %w
- new bucket reader: %w
AI-assisted analysis of argoproj/argo-workflows@35bff19146 (2026-09-03).
Data as JSON: /api/errors/1afc6f19d41beaca.
Report an issue: GitHub.