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
+4 -4
View File
@@ -354,10 +354,10 @@ pixa finds: `/etc/pixa/config.yml`, `/etc/pixa/config.yaml`,
`~/.config/pixa/config.yml`, `~/.config/pixa/config.yaml`, then `config.yml`
and `config.yaml` in the working directory. A named file that does not exist,
cannot be read or does not parse aborts startup. Of the files pixa looks for on
its own, one it finds but cannot read or parse aborts startup; one it cannot
find, for any reason, is passed over without a message, even when the file is
there in a directory pixa may not enter. With no file, pixa uses the environment
and the defaults.
its own, only one that does not exist is passed over, without a message. One
that pixa cannot read or parse aborts startup, naming the file. So does one in a
directory pixa may not enter, whether or not it is there, since pixa cannot
tell. With no file, pixa uses the environment and the defaults.
| Variable | Config key | Meaning |
| ------------------------------------ | ------------------------------- | ---------------------------------------------------------------------------- |
+5
View File
@@ -29,6 +29,11 @@ P2: security: referer blacklist
# Completed Steps
- 2026-10-04 a config file pixa cannot read aborts startup (closes #176): of the
places pixa looks for its config file on its own, only one where the file does
not exist is passed over; any other error, such as a directory on the path
that pixa may not enter, aborts startup naming the file, as a file that does
not parse already did.
- 2026-10-04 request IDs returned and passed on, and `/v1/e/` revalidates
(closes #84): pixa's own `RequestID` middleware, in place of chi's, gives each
request an ID, its own `X-Request-ID` when that is at most 64 letters, digits,
+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)