{"record":{"id":"d9964e31d7ae2a6c","repo":"gotify/server","slug":"read-file-for-s-file-s-w","errorCode":null,"errorMessage":"read file for %s_FILE (%s): %w","messagePattern":"read file for (.+?)_FILE \\((.+?)\\): %w","errorType":"console","errorClass":null,"httpStatus":null,"severity":"error","filePath":"config/parse.go","lineNumber":22,"sourceCode":"\t\"encoding/csv\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"os\"\n\t\"strconv\"\n\t\"strings\"\n)\n\nfunc lookupEnv(env string) (string, bool, error) {\n\tif raw, ok := os.LookupEnv(env); ok {\n\t\treturn raw, true, nil\n\t}\n\tpath, ok := os.LookupEnv(env + \"_FILE\")\n\tif !ok {\n\t\treturn \"\", false, nil\n\t}\n\tdata, err := os.ReadFile(path)\n\tif err != nil {\n\t\treturn \"\", false, fmt.Errorf(\"read file for %s_FILE (%s): %w\", env, path, err)\n\t}\n\treturn strings.TrimRight(string(data), \"\\r\\n\"), true, nil\n}\n\nfunc parseString(target *string, env string) error {\n\traw, ok, err := lookupEnv(env)\n\tif err != nil {\n\t\treturn err\n\t}\n\tif ok {\n\t\t*target = raw\n\t}\n\treturn nil\n}\n\nfunc parseInt(target *int, env string) error {\n\traw, ok, err := lookupEnv(env)\n\tif err != nil {","sourceCodeStart":4,"sourceCodeEnd":40,"githubUrl":"https://github.com/gotify/server/blob/14bfc256276775c425f988d621dccfe705de18ac/config/parse.go#L4-L40","documentation":"lookupEnv supports the _FILE convention: if <ENV>_FILE is set, the actual value is read from that file. If os.ReadFile fails, lookupEnv returns the wrapped error 'read file for %s_FILE (%s): %w', which propagates out of config.Get for every parser (parseString, parseInt, parseBool, parseList, parseMap, parseLogLevel).","triggerScenarios":"Setting an env var like API_KEY_FILE to a path that does not exist or is unreadable, then calling config.Get; the error surfaces on whichever var uses the _FILE mechanism.","commonSituations":"Docker/Kubernetes secrets mounted at a different path than _FILE points to, secret not mounted (typo in volume mount), file deleted at container start, or relative path resolved against the wrong working directory.","solutions":["Verify the file path in <VAR>_FILE exists and is readable inside the runtime environment (ls -l, check the mount).","Fix the Kubernetes/Docker volume mount so the secret is present at the exact path referenced by _FILE.","Use an absolute path for _FILE values.","Handle the error from config.Get at startup and fail fast with a message naming the variable and path."],"exampleFix":"// before\nAPI_KEY_FILE=/run/secrets/api_key  # file not mounted\n// after\nAPI_KEY_FILE=/run/secrets/api-key  # matches the actual volume mount path","handlingStrategy":"validation","validationCode":"func assertEnvFileReadable(env string) error {\n    p, ok := os.LookupEnv(env + \"_FILE\")\n    if !ok { return nil }\n    f, err := os.Open(p)\n    if err != nil {\n        return fmt.Errorf(\"%s_FILE=%s is not readable: %w\", env, p, err)\n    }\n    return f.Close()\n}\n// call before config.Get: assertEnvFileReadable(\"API_KEY\")","typeGuard":null,"tryCatchPattern":"if err := config.Get(&cfg); err != nil {\n    if m := fileVarRe.FindStringSubmatch(err.Error()); m != nil {\n        return fmt.Errorf(\"check secret mount for %s: %w\", m[1], err)\n    }\n    return err\n}","preventionTips":["Use absolute paths in every <VAR>_FILE and match them exactly to the secret mount","Verify secret mounts exist at container startup (readiness check that opens the file)","Keep _FILE and direct env mutually exclusive in your deployment templates","Fail fast at boot: surface config.Get errors before serving traffic"],"tags":["go","config","env","docker-secrets","file-io"],"backgroundTag":"env-file-unreadable","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"}