router-for-me/CLIProxyAPI · critical
failed to read config file: %w
Error message
failed to read config file: %w
What it means
Startup fails while reading the config file with os.ReadFile. In optional (cloud deploy) mode a missing file or directory is tolerated and returns an empty config; this error only escapes for other read failures — permission denied, path issues (a component of the path is not a directory), or the file being a directory in non-optional mode.
Source
Thrown at internal/config/config_load.go:44
return LoadConfigOptional(configFile, false)
}
// LoadConfigOptional reads YAML from configFile.
// If optional is true and the file is missing, it returns an empty Config.
// If optional is true and the file is empty or invalid, it returns an empty Config.
func LoadConfigOptional(configFile string, optional bool) (*Config, error) {
// Read the entire configuration file into memory.
data, err := os.ReadFile(configFile)
if err != nil {
if optional {
if os.IsNotExist(err) || errors.Is(err, syscall.EISDIR) {
// Missing and optional: return empty config (cloud deploy standby).
cfg := &Config{CredentialInFlight: DefaultCredentialInFlightConfig()}
cfg.NormalizePluginsConfig()
return cfg, nil
}
}
return nil, fmt.Errorf("failed to read config file: %w", err)
}
// In cloud deploy mode (optional=true), if file is empty or contains only whitespace, return empty config.
if optional && len(bytes.TrimSpace(data)) == 0 {
cfg := &Config{CredentialInFlight: DefaultCredentialInFlightConfig()}
cfg.NormalizePluginsConfig()
return cfg, nil
}
if errValidate := validateCredentialWeightYAML(data); errValidate != nil {
if optional {
cfgOptional := &Config{CredentialInFlight: DefaultCredentialInFlightConfig()}
cfgOptional.NormalizePluginsConfig()
return cfgOptional, nil
}
return nil, errValidate
}
View on GitHub (pinned to 78f0c4079e)
Solutions
- Check the file exists and is readable by the service user: ls -l <path> and sudo -u <user> cat <path>.
- Fix the --config flag or CONFIG path to point at the real file.
- If it is a directory or permission problem, correct ownership/mode (e.g. chmod 600 with the right owner).
- The wrapped %w error names the exact syscall reason — read it first.
Example fix
# before: unreadable config $ chmod 600 config.yaml # owned by root, server runs as appuser # after $ chown appuser: config.yaml && chmod 600 config.yaml
Defensive patterns
Strategy: validation
Validate before calling
// Pre-deploy check: file exists and is readable
info, err := os.Stat(configPath)
if err != nil { log.Fatal("config path invalid: ", err) }
if info.IsDir() { log.Fatal("config path is a directory") }
if info.Mode().Perm()&0o400 == 0 { log.Fatal("config not readable") } Prevention
- Verify the service user can read the file before every deploy.
- Pass an absolute path via --config to avoid cwd-dependent failures.
- The wrapped error contains the exact syscall reason (EACCES/ENOTDIR/...) — always read it.
When it happens
Trigger: LoadConfigOptional(configFile, optional=false) and os.ReadFile errors: EACCES (config not readable by the service user), ENOTDIR (path like /etc/config.yaml/x), EISDIR in non-optional mode, or I/O errors on the underlying disk.
Common situations: Running under systemd/container as a user without read permission on config.yaml; wrong --config path; file replaced by a directory by a provisioning script; NFS/mount hiccup.
Related errors
- failed to write file: %w
- failed to remove file: %w
- failed to update source auth file: %w
- failed to create directory: %v
- failed to create token file: %w
AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15).
Data as JSON: /api/errors/dbe6de6f687048b6.
Report an issue: GitHub.