Abort startup on a config file pixa cannot read (closes #176)
check / check (push) Failing after 2s

Of the places pixa looks for its config file on its own, it passed over
any place where os.Stat failed, so a file in a directory pixa may not
enter was skipped without a word and pixa started on a later file or on
the environment and defaults. Now only a file that does not exist is
passed over; any other error aborts startup naming the file, as a file
that does not parse already did. README.md says so where it gives the
search order.

Model: opus-5-5
This commit is contained in:
2026-10-04 11:12:48 +00:00
parent daa24d8b74
commit 4de726f109
3 changed files with 27 additions and 15 deletions
+18 -11
View File
@@ -4,6 +4,7 @@ package config
import (
"errors"
"fmt"
"io/fs"
"log/slog"
"math"
"net/netip"
@@ -778,19 +779,25 @@ func loadConfigFile(log *slog.Logger, appName string) (*smartconfig.Config, erro
for _, path := range configPaths {
cleanPath := filepath.Clean(path)
// Only a config file that does not exist is skipped. One that
// cannot be read or does not parse is a fatal startup error.
_, statErr := os.Stat(cleanPath)
if statErr == nil {
// A config file that exists but does not parse is a fatal
// startup error, never something to skip over.
sc, err := smartconfig.NewFromConfigPath(path)
if err != nil {
return nil, fmt.Errorf("failed to parse config file %s: %w", path, err)
}
log.Info("loaded config file", "path", path)
return sc, nil
if errors.Is(statErr, fs.ErrNotExist) {
continue
}
if statErr != nil {
return nil, fmt.Errorf("failed to read config file %s: %w", path, statErr)
}
sc, err := smartconfig.NewFromConfigPath(path)
if err != nil {
return nil, fmt.Errorf("failed to parse config file %s: %w", path, err)
}
log.Info("loaded config file", "path", path)
return sc, nil
}
return nil, nil //nolint:nilnil // nil config is valid (use defaults)