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

  1. Verify the file exists at the exact path: ls -l "$SOPS_AGE_KEY_FILE".
  2. Check permissions: the user running sops must be able to read it (chmod 600, correct owner).
  3. Use an absolute path (e.g. $HOME/.config/sops/age/keys.txt) to avoid working-directory surprises in CI/containers.
  4. Confirm the variable is exported and spelled SOPS_AGE_KEY_FILE in the environment running sops.
  5. 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

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


AI-assisted analysis of getsops/sops@13442bb981 (2026-09-01). Data as JSON: /api/errors/21a0b90571f1ff06. Report an issue: GitHub.