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

  1. Check the file exists and is readable by the service user: ls -l <path> and sudo -u <user> cat <path>.
  2. Fix the --config flag or CONFIG path to point at the real file.
  3. If it is a directory or permission problem, correct ownership/mode (e.g. chmod 600 with the right owner).
  4. 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

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


AI-assisted analysis of router-for-me/CLIProxyAPI@78f0c4079e (2026-08-15). Data as JSON: /api/errors/dbe6de6f687048b6. Report an issue: GitHub.