semaphoreui/semaphore · error
encryption_keys.keys_folder
Error message
encryption_keys.keys_folder %q: %w
What it means
loadKeysFolder wraps the os.ReadDir failure when encryption_keys.keys_folder cannot be listed. The wrapped OS error indicates the folder does not exist, is not a directory, or is not readable by the semaphore process.
Solutions
- Create the folder or fix the path: `mkdir -p <folder>` and place key files there
- Mount the volume/secret at the exact configured keys_folder path in your deployment
- Fix directory permissions (read+execute for the semaphore process user)
- Verify from inside the container: `ls -la <folder>`
Example fix
# before
encryption_keys:
keys_folder: /etc/semaphore/keys # never mounted
# after (K8s)
volumes:
- name: enc-keys
secret: {secretName: semaphore-enc-keys}
volumeMounts:
- {name: enc-keys, mountPath: /etc/semaphore/keys, readOnly: true}
encryption_keys:
keys_folder: /etc/semaphore/keys Defensive patterns
Strategy: validation
Validate before calling
if cfg.Encryption != nil && cfg.Encryption.KeysFolder != "" {
info, err := os.Stat(cfg.Encryption.KeysFolder)
if err != nil || !info.IsDir() {
return fmt.Errorf("keys_folder %s is not a readable directory", cfg.Encryption.KeysFolder)
}
} Type guard
func isReadableDir(path string) bool {
info, err := os.Stat(path)
return err == nil && info.IsDir()
} Try / catch
if err := loadKeysFolder(folder, addLabeled); err != nil {
return fmt.Errorf("cannot load encryption keys: %w", err)
} Prevention
- Match volumeMount mountPath in K8s/Docker exactly to keys_folder
- Create the directory in the image or entrypoint if keys are optional at mount time
- Run the container as a user with read+execute on the keys directory
- Smoke-test `ls -la <keys_folder>` inside the container as part of deployment checks
When it happens
Trigger: keys_folder set to a path that was never created/mounted; path is a file, not a directory; permission denied for the process user; typo in the mounted volume path.
Common situations: Kubernetes secret volume mounted at a different mountPath than configured; Docker volume not mounted in the container; directory created only on the host, not in the image; wrong ownership after chown.
Related errors
- : read key file
- encryption_keys.active.%s_key_file
- : 'value' and 'file' are mutually exclusive
- encryption_keys.active.%s_key: no key labelled
- encryption_keys.keys_folder: read
AI-assisted analysis of semaphoreui/semaphore@1774ccb71a (2026-09-07).
Data as JSON: /api/errors/30e181dc3ec2bdaa.
Report an issue: GitHub.
Appendix: source
Thrown at util/config.go:1576
return "", fmt.Errorf("encryption_keys.active.%s_key_file: %w", kind, err)
}
material := strings.TrimSpace(string(data))
if err := addLabeled(file, material); err != nil {
return "", err
}
return material, nil
}
return flat, nil
}
// loadKeysFolder reads every regular file in folder as one key, labelled by its
// filename. Dot-prefixed entries (e.g. Kubernetes' "..data" / "..2024_*") are
// 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)
}View on GitHub (pinned to 1774ccb71a)