gotify/server · error
cannot read config file %s: %w
Error message
cannot read config file %s: %w
What it means
config/migrate.Config reads the old YAML config file given on the command line so it can be converted to the new .env format. If os.ReadFile fails (missing path, permission denied, path is a directory), the OS error is wrapped as 'cannot read config file %s: %w' and returned to the migrate command.
Source
Thrown at config/migrate/migrate.go:80
OIDC struct {
Enabled *bool
Issuer *string
ClientID *string
ClientSecret *string
UsernameClaim *string
RedirectURL *string
AutoRegister *bool
Scopes []string
}
}
func Config(file string) (string, error) {
if file == "" {
return "", errors.New("migrate-config requires one argument: the path to the old config.yml")
}
data, err := os.ReadFile(file)
if err != nil {
return "", fmt.Errorf("cannot read config file %s: %w", file, err)
}
var migrated oldConfig
if err := yaml.Unmarshal(data, &migrated); err != nil {
return "", fmt.Errorf("cannot parse config file %s: %w", file, err)
}
content, err := godotenv.Marshal(buildEnv(migrated))
if err != nil {
return "", fmt.Errorf("cannot render config: %w", err)
}
return content, nil
}
func buildEnv(c oldConfig) map[string]string {
out := map[string]string{}
str := func(key string, value *string) {View on GitHub (pinned to 14bfc25627)
Solutions
- Check the path exists and is a regular file: ls -l <path>; fix typos or pass an absolute path.
- Fix permissions so the process user can read the file (chmod/chown or run as the right user).
- In containers, verify the old config is volume-mounted into the container at the given path.
- Handle the returned error in runMigrate by printing a clear message with the wrapped os error (e.g. os.IsNotExist) to guide the operator.
Example fix
// before
migrated, err := migrate.Config("config.old.yml") // relative path, wrong cwd
// after
migrated, err := migrate.Config("/etc/app/config.old.yml") // absolute, verified path Defensive patterns
Strategy: validation
Validate before calling
func assertReadable(path string) error {
info, err := os.Stat(path)
if err != nil {
return fmt.Errorf("old config %s: %w", path, err)
}
if info.IsDir() {
return fmt.Errorf("%s is a directory, want a file", path)
}
f, err := os.Open(path)
if err != nil { return err }
return f.Close()
} Try / catch
out, err := migrate.Config(oldPath)
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
return fmt.Errorf("old config not found at %s", oldPath)
}
if errors.Is(err, fs.ErrPermission) {
return fmt.Errorf("cannot read %s: check permissions", oldPath)
}
return err
} Prevention
- Use absolute paths for the old config when scripting the migration
- Check file readability with the same user that runs the migrate command
- In containers, verify the config volume mount before running migrate-config
- Keep a dry-run flag that stats the file before the real migration
When it happens
Trigger: Running the migrate-config command (run/runMigrate) with a path that does not exist, a path the process cannot read (permissions), or a directory instead of a file; also empty-file argument is rejected earlier with a different error.
Common situations: Typo in the config.yml path, running the migration from a different working directory with a relative path, Docker container not mounting the old config, or file owned by another user after deployment.
Understand the failure class
Background: "Config file not found": what it means and how to fix it in docker-sync, Maven, Vagrant, Turborepo and other tools — this error's family across 60 libraries.
Related errors
- cannot parse config file %s: %w
- cannot render config: %w
- read file for %s_FILE (%s): %w
- cannot delete internal application
- file with key 'file' must be present
AI-assisted analysis of gotify/server@14bfc25627 (2026-09-05).
Data as JSON: /api/errors/bcccab1bf5cfea60.
Report an issue: GitHub.