getsops/sops · error
failed to parse input as Bech32-encoded age public key: %w
Error message
failed to parse input as Bech32-encoded age public key: %w
What it means
parseRecipient matched the recipient as a hybrid age recipient (age1pq1 prefix) and called filippo.io/age's ParseHybridRecipient, which rejected the string — bad Bech32 checksum, wrong length, illegal characters, or truncated value. MasterKeyFromRecipient and Encrypt both funnel through parseRecipient, so encrypting/initializing a sops file with this recipient fails.
Source
Thrown at age/keysource.go:494
errs = append(errs, err)
} else {
identities = append(identities, ids...)
if len(ids) == 0 {
unusedLocations = append(unusedLocations, location)
}
}
}
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)View on GitHub (pinned to 13442bb981)
Solutions
- Re-copy the full recipient from `age-keygen -y key.txt` (or the plugin's public-key output) and verify it has the age1pq1 prefix with no whitespace.
- Validate the Bech32 string: correct character set (no 1, b, i, o except separators), correct length for the key type.
- If the key is actually a plain X25519 key, use its age1... form instead of forcing the hybrid prefix.
- Confirm sops and the age library versions support hybrid recipients; upgrade sops if the recipient was generated by a newer age tooling.
- Store recipients in .sops.yaml via quotes to prevent shell/YAML mangling.
Example fix
# before # .sops.yaml age: age1pq1t9h4x... # truncated on copy // failed to parse input as Bech32-encoded age public key: ... # after age-keygen -y key.txt > /tmp/pub # .sops.yaml age: age1pq1t9h4x...full-recipient... # pasted completely, no whitespace
Defensive patterns
Strategy: validation
Validate before calling
// Go/CI pre-check for hybrid recipients
func validHybridRecipient(r string) bool {
return strings.HasPrefix(r, "age1pq1") &&
!strings.ContainsAny(r, " \t\n\r\"'")
}
// plus one-time: `sops --encryption-round-trip` or age round-trip with the key Try / catch
rk, err := sopsage.MasterKeyFromRecipient(recipient)
if err != nil {
if strings.Contains(err.Error(), "Bech32-encoded age public key") {
return fmt.Errorf("recipient %q is not a valid bech32 key; re-copy from age-keygen -y", recipient)
}
return err
} Prevention
- Copy recipients directly from age-keygen -y output; never retype.
- Keep recipients in .sops.yaml under version control, one full line each.
- Add CI validation that every recipient parses via MasterKeyFromRecipient before encrypting.
- Beware Bech32 lookalikes (1/l, 0/o); validate instead of hand-fixing.
- Ensure all collaborators' sops versions support the hybrid format.
When it happens
Trigger: parseRecipient receives a recipient starting with age1pq1; age.ParseHybridRecipient returns an error for a malformed hybrid recipient string.
Common situations: Copy-paste truncated the recipient string; manual typing introduced an 'i' vs 'l' / '0' vs 'o' Bech32 confusion; newline or whitespace included in the recipient config; mixing up a hybrid recipient with a plain X25519 recipient from an older age/sops version.
Understand the failure class
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- failed to parse input as age-ssh public key: %w
- failed to parse input as age key from age plugin: %w
- incorrect passphrase
- failed to decrypt identity file: %v
- could not read passphrase: %v
AI-assisted analysis of getsops/sops@13442bb981 (2026-09-01).
Data as JSON: /api/errors/99940cb09162b4b9.
Report an issue: GitHub.