{"record":{"id":"03f4d5fa086fa463","repo":"gotify/server","slug":"cannot-render-config-w","errorCode":null,"errorMessage":"cannot render config: %w","messagePattern":"cannot render config: %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"config/migrate/migrate.go","lineNumber":90,"sourceCode":"}\n\nfunc Config(file string) (string, error) {\n\tif file == \"\" {\n\t\treturn \"\", errors.New(\"migrate-config requires one argument: the path to the old config.yml\")\n\t}\n\tdata, err := os.ReadFile(file)\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"cannot read config file %s: %w\", file, err)\n\t}\n\n\tvar migrated oldConfig\n\tif err := yaml.Unmarshal(data, &migrated); err != nil {\n\t\treturn \"\", fmt.Errorf(\"cannot parse config file %s: %w\", file, err)\n\t}\n\n\tcontent, err := godotenv.Marshal(buildEnv(migrated))\n\tif err != nil {\n\t\treturn \"\", fmt.Errorf(\"cannot render config: %w\", err)\n\t}\n\n\treturn content, nil\n}\n\nfunc buildEnv(c oldConfig) map[string]string {\n\tout := map[string]string{}\n\tstr := func(key string, value *string) {\n\t\tif value != nil {\n\t\t\tout[key] = *value\n\t\t}\n\t}\n\tnum := func(key string, value *int) {\n\t\tif value != nil {\n\t\t\tout[key] = strconv.Itoa(*value)\n\t\t}\n\t}\n\tboolean := func(key string, value *bool) {","sourceCodeStart":72,"sourceCodeEnd":108,"githubUrl":"https://github.com/gotify/server/blob/14bfc256276775c425f988d621dccfe705de18ac/config/migrate/migrate.go#L72-L108","documentation":"Once the old config is decoded, Config builds the env var map (buildEnv) and renders it as .env content with godotenv.Marshal. If Marshal fails, the error is wrapped as 'cannot render config: %w'. This is rare because Marshal mostly fails on values that cannot be represented as dotenv entries.","triggerScenarios":"godotenv.Marshal returns an error while serializing the map produced by buildEnv — typically when a migrated value contains characters that cannot be represented in a dotenv file without quoting issues in the installed godotenv version.","commonSituations":"Old config values containing newlines or special characters that break dotenv rendering, or a godotenv library version with stricter marshaling.","solutions":["Inspect the values buildEnv produces (log the map) and quote/escape problematic values (newlines, quotes, backslashes) before Marshal.","Upgrade or pin the godotenv dependency to a version whose Marshal handles your values.","Sanitize values in buildEnv (e.g. strconv.Quote strings with control characters).","As a fallback, render the map yourself with fmt.Printf(\"%s=%q\\n\", k, v) instead of godotenv.Marshal."],"exampleFix":"// before\ncontent, err := godotenv.Marshal(env) // fails on multiline secret\n// after\nfor k, v := range env { env[k] = strings.ReplaceAll(v, \"\\n\", \"\\\\n\") }\ncontent, err := godotenv.Marshal(env)","handlingStrategy":"try-catch","validationCode":"for k, v := range envMap {\n    if strings.ContainsAny(v, \"\\n\\r\") {\n        return fmt.Errorf(\"value for %s contains a newline and may fail dotenv rendering\", k)\n    }\n}","typeGuard":null,"tryCatchPattern":"content, err := migrate.Config(oldPath)\nif err != nil && strings.Contains(err.Error(), \"cannot render config\") {\n    // dump intermediate map to inspect offending values\n    return fmt.Errorf(\"rendering failed, check values for special chars: %w\", err)\n}","preventionTips":["Keep migrated values single-line; escape newlines and quotes in buildEnv","Pin the godotenv version and read its Marshal docs for escaping rules","Test migrate.Config against configs containing secrets with special characters"],"tags":["go","dotenv","config-migration","serialization"],"backgroundTag":"dotenv-render-failed","analyzedSha":"14bfc256276775c425f988d621dccfe705de18ac","analyzedAt":"2026-09-05T12:52:36.781Z","contentChangedAt":"2026-09-05T12:52:36.781Z","schemaVersion":2},"datasetVersion":"2026-09-12T17:17:11.597Z"}