argoproj/argo-workflows · error

unable to open file %s: %w

Error message

unable to open file %s: %w

What it means

PutFile opens the local file at `path` with os.Open before uploading to Azure. If the file cannot be opened (does not exist, permission denied, path is a broken symlink), the OS error is wrapped with this message. This is a local-filesystem failure, not an Azure failure.

Source

Thrown at workflow/artifacts/azure/azure.go:333

	containerClient, err := azblobDriver.newAzureContainerClient(ctx)
	if err != nil {
		return fmt.Errorf("unable to create Azure Blob Container client for %s: %w", outputArtifact.Azure.Blob, err)
	}

	blobClient := containerClient.NewBlockBlobClient(outputArtifact.Azure.Blob)
	if _, err = blobClient.UploadStream(ctx, reader, nil); err != nil {
		return fmt.Errorf("unable to upload stream to Azure blob %s: %w", outputArtifact.Azure.Blob, err)
	}
	return nil
}

// PutFile uploads a file to Azure Blob Storage
func PutFile(ctx context.Context, containerClient *container.Client, blobName, path string) error {
	blobClient := containerClient.NewBlockBlobClient(blobName)

	file, err := os.Open(path)
	if err != nil {
		return fmt.Errorf("unable to open file %s: %w", path, err)
	}
	defer func() {
		if closeErr := file.Close(); closeErr != nil {
			logger := logging.RequireLoggerFromContext(ctx)
			logger.WithError(closeErr).Warn(ctx, "unable to close file")
		}
	}()

	_, err = blobClient.UploadFile(ctx, file, nil)
	return err
}

// PutDirectory uploads all files in a directory to Azure Blob Storage
func PutDirectory(ctx context.Context, containerClient *container.Client, blobName, path string) error {
	for putTask := range generatePutTasks(blobName, path) {
		err := PutFile(ctx, containerClient, putTask.blobName, putTask.path)
		if err != nil {
			return err

View on GitHub (pinned to 35bff19146)

Solutions

  1. Confirm the file exists at the exact path (ls -l) inside the workflow container.
  2. Fix file permissions/ownership so the executor user can read it.
  3. Ensure no concurrent step deletes or moves the file before artifact save.
  4. Check for broken symlinks in directory artifacts.
  5. Use absolute paths and keep artifact paths inside the mounted volume.

Example fix

// before
path: /work/out.txt   // file deleted by a cleanup step
// after: keep until after save
main: generate → save artifact → cleanup container
Defensive patterns

Strategy: validation

Validate before calling

f, err := os.Open(path)
if err != nil { return err }
f.Close()
// safe to call PutFile now

Type guard

func readableFile(p string) bool {
  f, err := os.Open(p)
  if err != nil { return false }
  f.Close()
  return true
}

Try / catch

err := PutFile(ctx, containerClient, blobName, path)
if err != nil {
  var pe *fs.PathError
  if errors.As(err, &pe) && errors.Is(pe.Err, os.ErrNotExist) {
    // local file vanished — check producing step and cleanup ordering
  }
  return err
}

Prevention

When it happens

Trigger: PutFile called by Save/PutDirectory with a path that vanished or is unreadable: file removed during the workflow, wrong path passed, permission restrictions for the executor process.

Common situations: Race where a cleanup step deletes files before save; running as non-root while file is 0600 root-owned; symlink to a missing target; directory artifact where a child path was removed mid-walk.

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/67e2e889f53353fd. Report an issue: GitHub.