{"record":{"id":"0f864ae8c34c4a3c","repo":"semaphoreui/semaphore","slug":"encryption-keys-keys-folder-read-q-w","errorCode":null,"errorMessage":"encryption_keys.keys_folder: read %q: %w","messagePattern":"encryption_keys\\.keys_folder: read %q: %w","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"util/config.go","lineNumber":1590,"sourceCode":"// skipped; symlinks (how K8s mounts secret files) are followed via Stat.\nfunc loadKeysFolder(folder string, addLabeled func(string, string) error) error {\n\tentries, err := os.ReadDir(folder)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"encryption_keys.keys_folder %q: %w\", folder, err)\n\t}\n\tfor _, e := range entries {\n\t\tname := e.Name()\n\t\tif strings.HasPrefix(name, \".\") {\n\t\t\tcontinue\n\t\t}\n\t\tpath := filepath.Join(folder, name)\n\t\tinfo, err := os.Stat(path) // follow symlink\n\t\tif err != nil || !info.Mode().IsRegular() {\n\t\t\tcontinue\n\t\t}\n\t\tdata, err := os.ReadFile(path)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"encryption_keys.keys_folder: read %q: %w\", name, err)\n\t\t}\n\t\tif err := addLabeled(name, strings.TrimSpace(string(data))); err != nil {\n\t\t\treturn fmt.Errorf(\"encryption_keys.keys_folder: key %q: %w\", name, err)\n\t\t}\n\t}\n\treturn nil\n}\n\n// EncryptionKeysFile returns the configured keys-file path (encryption.keys_file),\n// or \"\" when no encryption section is configured.\nfunc (conf *ConfigType) EncryptionKeysFile() string {\n\tif conf.Encryption == nil {\n\t\treturn \"\"\n\t}\n\treturn conf.Encryption.KeysFile\n}\n\n// EncryptionKeysPollInterval returns how often the keys file is polled for","sourceCodeStart":1572,"sourceCodeEnd":1608,"githubUrl":"https://github.com/semaphoreui/semaphore/blob/1774ccb71a0a8b82eb74ea24c23ac9ab713de2fa/util/config.go#L1572-L1608","documentation":"While loading every regular file in encryption.keys_folder as an encryption key (labelled by filename), Semaphore's config loader calls os.ReadFile on each entry and wraps any read failure with this error. It means a key file that exists and passed the regular-file Stat check could not actually be read (permission denied, race deleting the file, I/O error). The startup key-resolution aborts.","triggerScenarios":"loadKeysFolder() iterates encryption.keys_folder; os.Stat succeeded on a regular file but the subsequent os.ReadFile failed (permissions changed between stat and read, file deleted by an atomic secret swap, NFS/disk I/O error).","commonSituations":"Kubernetes secret mounts where the symlink target was rotated between Stat and ReadFile; keys folder readable via symlink stat but the real file has restrictive ownership; disk errors on the mounted volume.","solutions":["Check file permissions/ownership of every file in the keys_folder so the semaphore process user can read them (chmod 644 / chown to the service user).","Re-check the folder for files that vanish or are rotated while the server boots; exclude non-key files instead of leaving unreadable ones in the folder.","Inspect the wrapped %w error (e.g. 'permission denied' vs 'no such file') to identify the exact failing file and fix the underlying OS cause.","If the folder is a network/cloud mount, verify the mount is healthy and retry the server start."],"exampleFix":"// before (unreadable file in keys folder)\nls -l /etc/semaphore/encryption-keys\n# -rw------- 1 root root key1  <- semaphore user cannot read\n// after\nsudo chown semaphore:semaphore /etc/semaphore/encryption-keys/*\nsudo chmod 600 /etc/semaphore/encryption-keys/*  # with matching process user","handlingStrategy":"validation","validationCode":"// Go: pre-flight every file in the keys folder before startup\nentries, _ := os.ReadDir(folder)\nfor _, e := range entries {\n    if strings.HasPrefix(e.Name(), \".\") { continue }\n    f, err := os.Open(filepath.Join(folder, e.Name()))\n    if err != nil { return fmt.Errorf(\"key %q unreadable: %w\", e.Name(), err) }\n    f.Close()\n}","typeGuard":null,"tryCatchPattern":"if err := util.ReloadEncryptionKeys(); err != nil {\n    log.Fatalf(\"encryption keys unreadable: %v\", err)\n}","preventionTips":["Run the server under a user that owns or can read the keys folder.","Avoid mixing rotated Kubernetes secret symlinks with strict permission checks; test mounts with the exact service account.","Keep only key files in the keys folder; store scripts/READMEs elsewhere."],"tags":["config","filesystem","encryption","startup"],"backgroundTag":"file-read-failed","analyzedSha":"1774ccb71a0a8b82eb74ea24c23ac9ab713de2fa","analyzedAt":"2026-09-07T11:00:33.293Z","contentChangedAt":"2026-09-07T11:00:33.293Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}