getsops/sops · error

failed to parse input as age key from age plugin

Error message

failed to parse input as age key from age plugin: %w

What it means

The recipient matched the age plugin format (age1... with multiple '1' separators, i.e. a plugin recipient like age1yubikey1...) and sops invoked the corresponding age plugin binary via plugin.NewRecipient, but the plugin failed — plugin binary missing, plugin erroring, or an invalid recipient string for that plugin. The chained error carries the plugin's own failure message.

Solutions

  1. Install the matching plugin binary and ensure it's on PATH (age-plugin-yubikey list, etc.).
  2. Run the plugin standalone to verify it works: age -r <recipient> round-trip test.
  3. In CI/non-TTY environments, provide the plugin's required secret via env (e.g. AGE_PLUGIN_YUBIKEY_PIN or the plugin's documented variable) since interactive prompts fail.
  4. Verify the recipient string matches what the plugin generated (plugin name/arguments embedded in the recipient).
  5. Upgrade sops and the plugin to compatible versions.

Example fix

// before (CI, no TTY, plugin needs PIN)
sops -e -r age1yubikey1q... secrets.yaml
// failed to parse input as age key from age plugin: plugin exited with error: PIN required

// after
export AGE_PLUGIN_YUBIKEY_PIN="123456"   # plugin's documented env var
sops -e -r age1yubikey1q... secrets.yaml
Defensive patterns

Strategy: validation

Validate before calling

// CI pre-flight: plugin must exist and recipient must be plugin-form
RCPT="age1yubikey1..."
case "$RCPT" in
  age1* ) echo ok;;
  * ) echo "bad recipient"; exit 1;;
esac
command -v age-plugin-yubikey >/dev/null || { echo "plugin missing"; exit 1; }

Try / catch

rk, err := sopsage.MasterKeyFromRecipient(recipient)
if err != nil && strings.Contains(err.Error(), "age key from age plugin") {
    return fmt.Errorf("age plugin failed for %q: install/authorize plugin or provide PIN env: %w", recipient, err)
}

Prevention

When it happens

Trigger: parseRecipient routes a multi-'1' age1 recipient to plugin.NewRecipient; the plugin executable is absent from PATH, exits with an error, cannot get a passphrase/PIN via the UI, or rejects the recipient.

Common situations: age-plugin-yubikey / age-plugin-sss not installed on a new machine; plugin requires a PIN interaction in a non-TTY CI job; recipient mistyped so the plugin can't resolve it; plugin version mismatch with the age library.

Understand the failure class

Related errors


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

Appendix: source

Thrown at age/keysource.go:501

	}
	return identities, unusedLocations, errs
}

// parseRecipient attempts to parse a string containing an encoded age public
// key or a public ssh key.
func parseRecipient(recipient string) (age.Recipient, error) {
	switch {
	case strings.HasPrefix(recipient, "age1pq1"):
		parsedRecipient, err := age.ParseHybridRecipient(recipient)
		if err != nil {
			return nil, fmt.Errorf("failed to parse input as Bech32-encoded age public key: %w", err)
		}

		return parsedRecipient, nil
	case strings.HasPrefix(recipient, "age1") && strings.Count(recipient, "1") > 1:
		parsedRecipient, err := plugin.NewRecipient(recipient, pluginTerminalUI)
		if err != nil {
			return nil, fmt.Errorf("failed to parse input as age key from age plugin: %w", err)
		}
		return parsedRecipient, nil
	case strings.HasPrefix(recipient, "age1"):
		parsedRecipient, err := age.ParseX25519Recipient(recipient)
		if err != nil {
			return nil, fmt.Errorf("failed to parse input as Bech32-encoded age public key: %w", err)
		}

		return parsedRecipient, nil
	case strings.HasPrefix(recipient, "ssh-"):
		parsedRecipient, err := agessh.ParseRecipient(recipient)
		if err != nil {
			return nil, fmt.Errorf("failed to parse input as age-ssh public key: %w", err)
		}
		return parsedRecipient, nil
	}

	return nil, fmt.Errorf("failed to parse input, unknown recipient type: %q", recipient)

View on GitHub (pinned to 13442bb981)