spf13/viper · error

failed to read configuration from input: %w

Error message

failed to read configuration from input: %w

What it means

Returned by unmarshalReader (viper.go:1787-1791) wrapping the error from buf.ReadFrom(in). It fires when the io.Reader itself fails to produce bytes — the format was resolved fine, but the read I/O errored. The underlying cause is preserved via %w for errors.Is/errors.As inspection.

Source

Thrown at viper.go:1790

	defer f.Close()

	if err := v.marshalWriter(f, configType); err != nil {
		return err
	}

	return f.Sync()
}

func (v *Viper) unmarshalReader(in io.Reader, c map[string]any) error {
	format := strings.ToLower(v.getConfigType())
	if format == "" {
		return errors.New("cannot decode configuration: unable to determine config type")
	}

	buf := new(bytes.Buffer)
	_, err := buf.ReadFrom(in)
	if err != nil {
		return fmt.Errorf("failed to read configuration from input: %w", err)
	}

	// TODO: remove this once SupportedExts is deprecated/removed
	if !slices.Contains(SupportedExts, format) {
		return UnsupportedConfigError(format)
	}

	// TODO: return [UnsupportedConfigError] if the registry does not contain the format
	// TODO: consider deprecating this error type
	decoder, err := v.decoderRegistry.Decoder(format)
	if err != nil {
		return ConfigParseError{err}
	}

	err = decoder.Decode(buf.Bytes(), c)
	if err != nil {
		return ConfigParseError{err}
	}

View on GitHub (pinned to 528f7416c4)

Solutions

  1. Inspect the wrapped error: if errors.Is(err, io.ErrClosedPipe) / os.ErrClosed -> the reader was closed.
  2. Ensure the reader stays open until ReadConfig returns (close with defer after the read, not before).
  3. For remote config, add retry with backoff around the ReadConfig call and verify the provider connection.

Example fix

// before
f, _ := os.Open("config.yaml")
f.Close()
_ = v.ReadConfig(f) // -> failed to read configuration from input: read ...: file already closed

// after
f, err := os.Open("config.yaml")
if err != nil { return err }
defer f.Close()
if err := v.ReadConfig(f); err != nil {
    var pathErr *fs.PathError
    if errors.As(err, &pathErr) { /* handle I/O */ }
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Ensure the reader is usable before handing it to Viper.
func safeOpenConfig(path string) (io.ReadCloser, error) {
    f, err := os.Open(path)
    if err != nil { return nil, err }
    // probe a single byte to catch already-closed/permission issues early
    buf := make([]byte, 1)
    if _, err := f.Read(buf); err != nil && err != io.EOF {
        f.Close()
        return nil, err
    }
    if _, err := f.Seek(0, io.SeekStart); err != nil { f.Close(); return nil, err }
    return f, nil
}

Try / catch

if err := v.ReadConfig(r); err != nil {
    var wrapped *wrappedErr // surface inner via errors.Is/errors.As on the %w chain
    if errors.Is(err, os.ErrClosed) {
        // reader was closed before ReadConfig
    } else if errors.Is(err, io.ErrUnexpectedEOF) {
        // truncated stream
    }
}

Prevention

When it happens

Trigger: ReadConfig with a *os.File that was already Closed; reading from a remote key-value provider (consul/etcd via viper/remote) whose Get returns a broken or short reader; reading from a pipe/network reader that errors mid-stream; bytes/strings readers essentially never trigger this.

Common situations: defer f.Close() placed before ReadConfig runs (or Close called explicitly too early); transient network failure reading remote config; corrupted/garbage stream from a misbehaving provider; permission denied re-opening on a WatchConfig reload.

Related errors


AI-assisted analysis of spf13/viper@528f7416c4 (2026-08-04). Data as JSON: /data/errors/4b1fbabc3ec271aa.json. Report an issue: GitHub.