getsops/sops · error
failed to open file: %w
Error message
failed to open file: %w
What it means
The default age key file at <userConfigDir>/sops/age/keys.txt exists as a path but could not be opened — the open failed with something other than ErrNotExist (e.g. a permissions error, or the path is a directory). This is a candidate-location failure collected during loadIdentities.
Source
Thrown at age/keysource.go:461
errs = append(errs, err)
} else {
readers[SopsAgeKeyCmdEnv] = identityReader{
reader: bytes.NewReader(out),
allowMultipleKeysPerLine: false,
}
}
} else {
unusedLocations = append(unusedLocations, SopsAgeKeyCmdEnv)
}
userConfigDir, err := getUserConfigDir()
if err != nil && len(readers) == 0 && len(identities) == 0 {
errs = append(errs, fmt.Errorf("user config directory could not be determined: %w", err))
} else if userConfigDir != "" {
ageKeyFilePath := filepath.Join(userConfigDir, filepath.FromSlash(SopsAgeKeyUserConfigPath))
f, err := os.Open(ageKeyFilePath)
if err != nil && !errors.Is(err, os.ErrNotExist) {
errs = append(errs, fmt.Errorf("failed to open file: %w", err))
} else if errors.Is(err, os.ErrNotExist) && len(readers) == 0 && len(identities) == 0 {
unusedLocations = append(unusedLocations, ageKeyFilePath)
} else if err == nil {
defer f.Close()
readers[ageKeyFilePath] = identityReader{
reader: f,
allowMultipleKeysPerLine: false,
}
}
}
for location, r := range readers {
ids, err := unwrapIdentities(location, r.reader, r.allowMultipleKeysPerLine)
if err != nil {
errs = append(errs, err)
} else {
identities = append(identities, ids...)
if len(ids) == 0 {View on GitHub (pinned to 13442bb981)
Solutions
- Check ownership and permissions: ls -l ~/.config/sops/age/keys.txt and chmod 600 with correct owner.
- Verify the path is a regular file, not a directory (the error 'is a directory' means a mount/volume collision).
- If run under a service account, grant that user read access instead of relying on your own shell user.
- In containers, ensure the secret is mounted as a file (subPath in Kubernetes) at ~/.config/sops/age/keys.txt.
- Fallback: point SOPS_AGE_KEY_FILE directly at the keys file to bypass the default-location probe.
Example fix
# before $ ls -l ~/.config/sops/age/keys.txt -rw------- 1 root root ... keys.txt # owned by root, sops runs as app // failed to open file: open /home/app/.config/sops/age/keys.txt: permission denied # after sudo chown app:app ~/.config/sops/age/keys.txt chmod 600 ~/.config/sops/age/keys.txt
Defensive patterns
Strategy: validation
Validate before calling
// shell: validate the default key file before invoking sops
KEY="$HOME/.config/sops/age/keys.txt"
[ -f "$KEY" ] && [ -r "$KEY" ] && [ ! -d "$KEY" ] \
|| { echo "age key file unusable: $KEY"; exit 1; } Try / catch
if err := runSopsDecrypt(); err != nil &&
strings.Contains(err.Error(), "failed to open file") {
return fmt.Errorf("default age keys.txt unreadable; check owner/perms or set SOPS_AGE_KEY_FILE: %w", err)
} Prevention
- Provision keys.txt with the running user's ownership and chmod 600; avoid sudo-created files.
- In Kubernetes, mount the secret with subPath so it lands as a file, not a directory.
- Include a permissions check in bootstrap scripts before first sops use.
- If the default path is problematic, set SOPS_AGE_KEY_FILE explicitly as a bypass.
- Watch for umask issues creating 000-permission files.
When it happens
Trigger: loadIdentities builds ageKeyFilePath from getUserConfigDir plus SopsAgeKeyUserConfigPath, calls os.Open, and gets a non-ErrNotExist error (EACCES, EISDIR, too many open files, etc.).
Common situations: keys.txt copied with root ownership (chmod'd wrong) after sudo provisioning; secrets mounted as directories in Kubernetes/containers instead of files; keys.txt created with 000 permissions.
Understand the failure class
Background: "open() failed", "failed to open file", "cannot create file" — what a file open error means and how to fix it — this error's family across 42 libraries.
Related errors
- failed to open %s file: %w
- failed to read '%s': %w
- incorrect passphrase
- failed to decrypt identity file: %v
- could not read passphrase: %v
AI-assisted analysis of getsops/sops@13442bb981 (2026-09-01).
Data as JSON: /api/errors/9ce482009ce2e9ff.
Report an issue: GitHub.