getsops/sops · error
failed to open %s file: %w
Error message
failed to open %s file: %w
What it means
SOPS looked for age identities in the file pointed to by the SOPS_AGE_KEY_FILE environment variable and os.Open failed — typically because the path doesn't exist, is a directory, or is unreadable. The error is collected as one of the candidate-location failures during loadIdentities; if no other key source succeeds, decryption fails with this error chained in the aggregate.
Source
Thrown at age/keysource.go:428
// found references, and expects at least one configuration to be present.
func (key *MasterKey) loadIdentities() (ParsedIdentities, []string, errSet) {
identities, unusedLocations, errs := key.loadAgeSSHIdentities()
var readers = make(map[string]identityReader, 0)
if ageKey, ok := os.LookupEnv(SopsAgeKeyEnv); ok {
readers[SopsAgeKeyEnv] = identityReader{
reader: strings.NewReader(ageKey),
allowMultipleKeysPerLine: true,
}
} else {
unusedLocations = append(unusedLocations, SopsAgeKeyEnv)
}
if ageKeyFile, ok := os.LookupEnv(SopsAgeKeyFileEnv); ok {
f, err := os.Open(ageKeyFile)
if err != nil {
errs = append(errs, fmt.Errorf("failed to open %s file: %w", SopsAgeKeyFileEnv, err))
} else {
defer f.Close()
readers[SopsAgeKeyFileEnv] = identityReader{
reader: f,
allowMultipleKeysPerLine: false,
}
}
} else {
unusedLocations = append(unusedLocations, SopsAgeKeyFileEnv)
}
if ageKeyCmd, ok := os.LookupEnv(SopsAgeKeyCmdEnv); ok {
out, err := getOutputFromCmd(ageKeyCmd, []string{fmt.Sprintf("%s=%s", SopsAgeRecipientEnv, key.Recipient)})
if err != nil {
errs = append(errs, err)
} else {
readers[SopsAgeKeyCmdEnv] = identityReader{
reader: bytes.NewReader(out),View on GitHub (pinned to 13442bb981)
Solutions
- Verify the file exists at the exact path: ls -l "$SOPS_AGE_KEY_FILE".
- Check permissions: the user running sops must be able to read it (chmod 600, correct owner).
- Use an absolute path (e.g. $HOME/.config/sops/age/keys.txt) to avoid working-directory surprises in CI/containers.
- Confirm the variable is exported and spelled SOPS_AGE_KEY_FILE in the environment running sops.
- As an alternative, place the key at the default location ~/.config/sops/age/keys.txt and unset SOPS_AGE_KEY_FILE.
Example fix
// before export SOPS_AGE_KEY_FILE="./keys.txt" # not in CI working dir // failed to open SOPS_AGE_KEY_FILE file: open ./keys.txt: no such file or directory // after export SOPS_AGE_KEY_FILE="$HOME/.config/sops/age/keys.txt" ls -l "$SOPS_AGE_KEY_FILE" # verify readable before running sops
Defensive patterns
Strategy: validation
Validate before calling
// shell pre-flight
if [ -n "$SOPS_AGE_KEY_FILE" ]; then
[ -f "$SOPS_AGE_KEY_FILE" ] || { echo "not a readable file: $SOPS_AGE_KEY_FILE"; exit 1; }
[ -r "$SOPS_AGE_KEY_FILE" ] || { echo "not readable: $SOPS_AGE_KEY_FILE"; exit 1; }
fi Try / catch
out, err := runSopsDecrypt()
if err != nil && strings.Contains(err.Error(), "failed to open "+"SOPS_AGE_KEY_FILE") {
return fmt.Errorf("check SOPS_AGE_KEY_FILE path/permissions: %w", err)
} Prevention
- Use absolute paths anchored on $HOME; never relative paths in shared scripts.
- Provision the key file (and chmod 600) during machine/CI bootstrap.
- Verify with ls -l "$SOPS_AGE_KEY_FILE" in setup steps.
- Keep the file a regular file; mount secrets as files, not directories.
- Export the variable in the same environment that runs sops (CI env mapping, direnv).
When it happens
Trigger: SOPS_AGE_KEY_FILE is set, loadIdentities calls os.Open on its value, and the open fails (ENOENT, EACCES, EISDIR).
Common situations: Typo in the path or filename; file deleted after provisioning; path valid for the user but sops runs in a container/CI where it was never copied; relative path resolved against a different working directory; pointing at a directory instead of a file.
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 file: %w
- failed to read '%s': %w
- failed to parse command %s: %w
- failed to execute command %s: %w
- user config directory could not be determined: %w
AI-assisted analysis of getsops/sops@13442bb981 (2026-09-01).
Data as JSON: /api/errors/21a0b90571f1ff06.
Report an issue: GitHub.