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
- Inspect the wrapped error: if errors.Is(err, io.ErrClosedPipe) / os.ErrClosed -> the reader was closed.
- Ensure the reader stays open until ReadConfig returns (close with defer after the read, not before).
- 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
- Close readers with defer AFTER ReadConfig returns, not before.
- For remote config, wrap ReadConfig in retry-with-backoff and surface provider errors.
- Always inspect the wrapped error (%w) rather than matching the top-level string.
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.