semaphoreui/semaphore · error

encryption_keys.active.%s_key_file

Error message

encryption_keys.active.%s_key_file: %w

What it means

resolveActiveKey wraps the os.ReadFile failure for encryption_keys.active.<kind>_file. Relative paths are resolved against encryption_keys.keys_folder; the wrapped OS error (ENOENT, permissions, directory) is preserved via %w.

Solutions

  1. Verify the file exists where it resolves: keys_folder/<file> (or the absolute path given): `ls -l <resolved-path>`
  2. Set encryption_keys.keys_folder correctly so relative filenames resolve
  3. Remount/mount the secret volume containing the key file
  4. Fix permissions so the semaphore process can read the file

Example fix

# before
encryption_keys:
  active:
    option_key_file: option.key   # file missing
  keys_folder: /etc/semaphore/keys
# after  (place option.key into /etc/semaphore/keys)
encryption_keys:
  active:
    option_key_file: option.key
  keys_folder: /etc/semaphore/keys   # must contain option.key
Defensive patterns

Strategy: validation

Validate before calling

path := cfg.Encryption.Active.OptionKeyFile
if !filepath.IsAbs(path) && cfg.Encryption.KeysFolder != "" {
    path = filepath.Join(cfg.Encryption.KeysFolder, path)
}
if _, err := os.Stat(path); err != nil {
    return fmt.Errorf("active.option_key_file resolves to missing file %s", path)
}

Type guard

func keyFileReadable(cfg *util.EncryptionKeysConfig, file string) bool {
    if file == "" { return false }
    p := file
    if !filepath.IsAbs(p) && cfg != nil { p = filepath.Join(cfg.KeysFolder, p) }
    _, err := os.Stat(p)
    return err == nil
}

Try / catch

material, err := resolveActiveKey(enc, flat, byLabel, addLabeled, ptr, "option")
if err != nil {
    return nil, fmt.Errorf("active key file problem: %w", err)
}

Prevention

When it happens

Trigger: active.option_key_file / secret_key_file naming a file that does not exist in keys_folder (or at the absolute path), the folder not being mounted, or permissions blocking the read.

Common situations: K8s secret mounted at a different path than configured; file deleted during key rotation before the config was updated; relative filename used but keys_folder not set; symlink targets unreadable.

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


AI-assisted analysis of semaphoreui/semaphore@1774ccb71a (2026-09-07). Data as JSON: /api/errors/c518013e13e48017. Report an issue: GitHub.

Appendix: source

Thrown at util/config.go:1558

	if label != "" {
		material, ok := byLabel[label]
		if !ok {
			return "", fmt.Errorf("encryption_keys.active.%s_key: no key labelled %q", kind, label)
		}
		return material, nil
	}

	if file != "" {
		if material, ok := byLabel[file]; ok {
			return material, nil
		}
		path := file
		if !filepath.IsAbs(path) && enc != nil {
			path = filepath.Join(enc.KeysFolder, file)
		}
		data, err := os.ReadFile(path)
		if err != nil {
			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)

View on GitHub (pinned to 1774ccb71a)