grpc-ecosystem/grpc-gateway · error

read %s: %w

Error message

read %s: %w

What it means

run reads each input file with os.ReadFile and wraps any OS-level read failure as `read <path>: <underlying error>`. This is the standard go idiom for file I/O errors: the wrapped error preserves the exact cause (file not found, permission denied, is a directory, etc.) while naming the path that failed.

Source

Thrown at openapiv3-merge/main.go:46

func main() {
	if err := run(os.Args[1:], os.Stdout); err != nil {
		fmt.Fprintln(os.Stderr, "openapiv3-merge:", err)
		os.Exit(1)
	}
}

// run is the testable entry point. It accepts the program's arguments (no
// program name) and the writer to emit the merged document to.
func run(args []string, out io.Writer) error {
	if len(args) == 0 {
		return errors.New("usage: openapiv3-merge FILE [FILE ...]")
	}
	inputs := make([]merge.Input, 0, len(args))
	for _, path := range args {
		data, err := os.ReadFile(path)
		if err != nil {
			return fmt.Errorf("read %s: %w", path, err)
		}
		inputs = append(inputs, merge.Input{Name: path, Data: data})
	}
	merged, err := merge.Merge(inputs)
	if err != nil {
		return err
	}
	if _, err := out.Write(merged); err != nil {
		return err
	}
	if _, err := io.WriteString(out, "\n"); err != nil {
		return err
	}
	return nil
}

View on GitHub (pinned to a58a4436a3)

Solutions

  1. Check the path with `ls -l <path>`; fix typos and run from the correct working directory
  2. If the error is permission denied, adjust file permissions or run with appropriate access
  3. Confirm the file exists inside the container/CI environment (mount volumes, copy artifacts)
  4. Verify you pass file paths, not directory paths
  5. Use absolute paths or paths relative to the actual CWD

Example fix

// before
openapiv3-merge ./specs/api.jsoin
// after
openapiv3-merge ./specs/api.json
Defensive patterns

Strategy: try-catch

Validate before calling

for _, path := range args {
    info, err := os.Stat(path)
    if err != nil {
        return fmt.Errorf("input %s is not accessible: %w", path, err)
    }
    if info.IsDir() {
        return fmt.Errorf("input %s is a directory, expected a file", path)
    }
}

Try / catch

if err := run(os.Args[1:]); err != nil {
    var pathErr *fs.PathError
    if errors.As(err, &pathErr) && errors.Is(pathErr.Err, fs.ErrNotExist) {
        fmt.Fprintf(os.Stderr, "file not found: %s\n", pathErr.Path)
        os.Exit(1)
    }
    if errors.As(err, &pathErr) && errors.Is(pathErr.Err, fs.ErrPermission) {
        fmt.Fprintf(os.Stderr, "permission denied: %s\n", pathErr.Path)
        os.Exit(1)
    }
    fmt.Fprintln(os.Stderr, err)
    os.Exit(1)
}

Prevention

When it happens

Trigger: Invoking `openapiv3-merge FILE...` with a path that does not exist (ENOENT), is unreadable due to permissions (EACCES), is a directory, or otherwise fails os.ReadFile. Also triggered by tests calling run with a missing file path.

Common situations: Typo in the filename or wrong working directory; running inside a container where the spec file was not mounted; the spec file moved after code generation; running in CI where artifacts are in a different path; permission-restricted files.

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 grpc-ecosystem/grpc-gateway@a58a4436a3 (2026-09-02). Data as JSON: /api/errors/578243aab55212bd. Report an issue: GitHub.