semaphoreui/semaphore · error
encryption_keys.keys_folder: read
Error message
encryption_keys.keys_folder: read %q: %w
What it means
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.
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.
Example fix
// before (unreadable file in keys folder) ls -l /etc/semaphore/encryption-keys # -rw------- 1 root root key1 <- semaphore user cannot read // after sudo chown semaphore:semaphore /etc/semaphore/encryption-keys/* sudo chmod 600 /etc/semaphore/encryption-keys/* # with matching process user
Defensive patterns
Strategy: validation
Validate before calling
// Go: pre-flight every file in the keys folder before startup
entries, _ := os.ReadDir(folder)
for _, e := range entries {
if strings.HasPrefix(e.Name(), ".") { continue }
f, err := os.Open(filepath.Join(folder, e.Name()))
if err != nil { return fmt.Errorf("key %q unreadable: %w", e.Name(), err) }
f.Close()
} Try / catch
if err := util.ReloadEncryptionKeys(); err != nil {
log.Fatalf("encryption keys unreadable: %v", err)
} Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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.
Understand the failure class
Background: "failed to read file", EACCES, ENOENT and "could not read <path>" errors: when a program can't read a file from disk — this error's family across 49 libraries.
Related errors
- : read key file
- encryption_keys.active.%s_key_file
- encryption_keys.keys_folder
- You can't use both HTTP redirect address and port at the…
- : 'value' and 'file' are mutually exclusive
AI-assisted analysis of semaphoreui/semaphore@1774ccb71a (2026-09-07).
Data as JSON: /api/errors/0f864ae8c34c4a3c.
Report an issue: GitHub.
Appendix: source
Thrown at util/config.go:1590
// skipped; symlinks (how K8s mounts secret files) are followed via Stat.
func loadKeysFolder(folder string, addLabeled func(string, string) error) error {
entries, err := os.ReadDir(folder)
if err != nil {
return fmt.Errorf("encryption_keys.keys_folder %q: %w", folder, err)
}
for _, e := range entries {
name := e.Name()
if strings.HasPrefix(name, ".") {
continue
}
path := filepath.Join(folder, name)
info, err := os.Stat(path) // follow symlink
if err != nil || !info.Mode().IsRegular() {
continue
}
data, err := os.ReadFile(path)
if err != nil {
return fmt.Errorf("encryption_keys.keys_folder: read %q: %w", name, err)
}
if err := addLabeled(name, strings.TrimSpace(string(data))); err != nil {
return fmt.Errorf("encryption_keys.keys_folder: key %q: %w", name, err)
}
}
return nil
}
// EncryptionKeysFile returns the configured keys-file path (encryption.keys_file),
// or "" when no encryption section is configured.
func (conf *ConfigType) EncryptionKeysFile() string {
if conf.Encryption == nil {
return ""
}
return conf.Encryption.KeysFile
}
// EncryptionKeysPollInterval returns how often the keys file is polled forView on GitHub (pinned to 1774ccb71a)