argoproj/argo-workflows · error

test if %s is a dir: %w

Error message

test if %s is a dir: %w

What it means

uploadObjects uploads a local path to GCS; first it checks with file.IsDirectory(path) whether the source is a directory. This error wraps a failure of that check itself. It throws when the driver cannot even stat/open the local source path, not when the upload fails.

Source

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

	for _, file := range files {
		if file.IsDir() {
			fs, err := listFileRelPaths(path+file.Name()+string(os.PathSeparator), relPath+file.Name()+string(os.PathSeparator))
			if err != nil {
				return nil, err
			}
			results = append(results, fs...)
		} else {
			results = append(results, relPath+file.Name())
		}
	}
	return results, nil
}

// upload a local file or dir to GCS
func uploadObjects(ctx context.Context, client *storage.Client, bucket, key, path string) error {
	isDir, err := file.IsDirectory(path)
	if err != nil {
		return fmt.Errorf("test if %s is a dir: %w", path, err)
	}
	if isDir {
		dirName := filepath.Clean(path) + string(os.PathSeparator)
		keyPrefix := filepath.Clean(key) + "/"
		fileRelPaths, listErr := listFileRelPaths(dirName, "")
		if listErr != nil {
			return listErr
		}
		for _, relPath := range fileRelPaths {
			fullKey := normalizeGCSKey(keyPrefix + relPath)
			err = uploadObject(ctx, client, bucket, fullKey, dirName+relPath)
			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)

View on GitHub (pinned to 35bff19146)

Solutions

  1. Verify the template actually creates the file/directory at the artifact's path
  2. Check the artifact path spelling and that the volume is mounted there
  3. Add an `ls -la`/debug step or use argo logs to inspect the container filesystem
  4. Check permissions of the path for the container user

Example fix

// before
artifacts: [{name: result, path: /out/result.json}]  # script writes ./result.json
// after
container:
  command: [sh, -c, 'mkdir -p /out && cp result.json /out/']
artifacts: [{name: result, path: /out/result.json}]
Defensive patterns

Strategy: validation

Validate before calling

// inside the producing step, before it exits:
import "os"
func assertPathExists(p string) {
	if _, err := os.Stat(p); err != nil {
		panic(fmt.Sprintf("artifact source missing: %v", err))
	}
}
// assertPathExists("/out/result.json") at end of script

Try / catch

if err := saveArtifacts(ctx); err != nil {
	if strings.Contains(err.Error(), "test if ") {
		// local source missing: check the producing step's outputs before retrying
	}
	return err
}

Prevention

When it happens

Trigger: file.IsDirectory returns an error for the artifact source path during artifact Save: the path does not exist, permission denied, or the path is on a broken mount — i.e. os.Stat/open failed.

Common situations: Artifact path never produced by the step (script wrote elsewhere or failed silently); volume not mounted at the expected path; output parameter/artifact path typo in the template.

Related errors


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