getsops/sops · error

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

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.

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)

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.