{"record":{"id":"9ce482009ce2e9ff","repo":"getsops/sops","slug":"failed-to-open-file-w","errorCode":null,"errorMessage":"failed to open file: %w","messagePattern":"failed to open file: %w","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"age/keysource.go","lineNumber":461,"sourceCode":"\t\t\terrs = append(errs, err)\n\t\t} else {\n\t\t\treaders[SopsAgeKeyCmdEnv] = identityReader{\n\t\t\t\treader:                   bytes.NewReader(out),\n\t\t\t\tallowMultipleKeysPerLine: false,\n\t\t\t}\n\t\t}\n\t} else {\n\t\tunusedLocations = append(unusedLocations, SopsAgeKeyCmdEnv)\n\t}\n\n\tuserConfigDir, err := getUserConfigDir()\n\tif err != nil && len(readers) == 0 && len(identities) == 0 {\n\t\terrs = append(errs, fmt.Errorf(\"user config directory could not be determined: %w\", err))\n\t} else if userConfigDir != \"\" {\n\t\tageKeyFilePath := filepath.Join(userConfigDir, filepath.FromSlash(SopsAgeKeyUserConfigPath))\n\t\tf, err := os.Open(ageKeyFilePath)\n\t\tif err != nil && !errors.Is(err, os.ErrNotExist) {\n\t\t\terrs = append(errs, fmt.Errorf(\"failed to open file: %w\", err))\n\t\t} else if errors.Is(err, os.ErrNotExist) && len(readers) == 0 && len(identities) == 0 {\n\t\t\tunusedLocations = append(unusedLocations, ageKeyFilePath)\n\t\t} else if err == nil {\n\t\t\tdefer f.Close()\n\t\t\treaders[ageKeyFilePath] = identityReader{\n\t\t\t\treader:                   f,\n\t\t\t\tallowMultipleKeysPerLine: false,\n\t\t\t}\n\t\t}\n\t}\n\n\tfor location, r := range readers {\n\t\tids, err := unwrapIdentities(location, r.reader, r.allowMultipleKeysPerLine)\n\t\tif err != nil {\n\t\t\terrs = append(errs, err)\n\t\t} else {\n\t\t\tidentities = append(identities, ids...)\n\t\t\tif len(ids) == 0 {","sourceCodeStart":443,"sourceCodeEnd":479,"githubUrl":"https://github.com/getsops/sops/blob/13442bb98183887d7a9ac09ec8ab0564673a59d8/age/keysource.go#L443-L479","documentation":"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.","triggerScenarios":"loadIdentities builds ageKeyFilePath from getUserConfigDir plus SopsAgeKeyUserConfigPath, calls os.Open, and gets a non-ErrNotExist error (EACCES, EISDIR, too many open files, etc.).","commonSituations":"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.","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."],"exampleFix":"# before\n$ ls -l ~/.config/sops/age/keys.txt\n-rw------- 1 root root ... keys.txt   # owned by root, sops runs as app\n// failed to open file: open /home/app/.config/sops/age/keys.txt: permission denied\n\n# after\nsudo chown app:app ~/.config/sops/age/keys.txt\nchmod 600 ~/.config/sops/age/keys.txt","handlingStrategy":"validation","validationCode":"// shell: validate the default key file before invoking sops\nKEY=\"$HOME/.config/sops/age/keys.txt\"\n[ -f \"$KEY\" ] && [ -r \"$KEY\" ] && [ ! -d \"$KEY\" ] \\\n  || { echo \"age key file unusable: $KEY\"; exit 1; }","typeGuard":null,"tryCatchPattern":"if err := runSopsDecrypt(); err != nil &&\n   strings.Contains(err.Error(), \"failed to open file\") {\n    return fmt.Errorf(\"default age keys.txt unreadable; check owner/perms or set SOPS_AGE_KEY_FILE: %w\", err)\n}","preventionTips":["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."],"tags":["age","file-io","permissions"],"backgroundTag":"file-open-failed","analyzedSha":"13442bb98183887d7a9ac09ec8ab0564673a59d8","analyzedAt":"2026-09-01T03:53:00.447Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}