{"record":{"id":"c776ee3173edfd74","repo":"getsops/sops","slug":"could-not-read-passphrase-v","errorCode":null,"errorMessage":"could not read passphrase: %v","messagePattern":"could not read passphrase: (.+?)","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"age/encrypted_keys.go","lineNumber":99,"sourceCode":"// ScryptIdentity.\ntype LazyScryptIdentity struct {\n\tPassphrase func() (string, error)\n}\n\nvar _ age.Identity = &LazyScryptIdentity{}\n\nfunc (i *LazyScryptIdentity) Unwrap(stanzas []*age.Stanza) (fileKey []byte, err error) {\n\tfor _, s := range stanzas {\n\t\tif s.Type == \"scrypt\" && len(stanzas) != 1 {\n\t\t\treturn nil, errors.New(\"an scrypt recipient must be the only one\")\n\t\t}\n\t}\n\tif len(stanzas) != 1 || stanzas[0].Type != \"scrypt\" {\n\t\treturn nil, age.ErrIncorrectIdentity\n\t}\n\tpass, err := i.Passphrase()\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"could not read passphrase: %v\", err)\n\t}\n\tii, err := age.NewScryptIdentity(pass)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tfileKey, err = ii.Unwrap(stanzas)\n\treturn fileKey, err\n}\n\nfunc unwrapIdentities(location string, reader io.Reader, allowMultipleKeysPerLine bool) (ParsedIdentities, error) {\n\tb := bufio.NewReader(reader)\n\tp, _ := b.Peek(14) // length of \"age-encryption\" and \"-----BEGIN AGE\"\n\tpeeked := string(p)\n\n\tswitch {\n\t// An age encrypted file, plain or armored.\n\tcase peeked == \"age-encryption\" || peeked == \"-----BEGIN AGE\":\n\t\tvar r io.Reader = b","sourceCodeStart":81,"sourceCodeEnd":117,"githubUrl":"https://github.com/getsops/sops/blob/13442bb98183887d7a9ac09ec8ab0564673a59d8/age/encrypted_keys.go#L81-L117","documentation":"LazyScryptIdentity.Unwrap needs a passphrase to build a ScryptIdentity; it obtains one from the configured Passphrase callback. If that callback returns an error (user aborted the prompt, no TTY available, agent failure), it is wrapped as \"could not read passphrase\". The error reflects failure to obtain the secret, not that it was wrong.","triggerScenarios":"Calling Unwrap on a scrypt stanza when i.Passphrase() errors — no interactive terminal to prompt in, CI run without SOPS_AGE_PASSPHRASE set, or the passphrase program/agent exits non-zero.","commonSituations":"Running sops in non-interactive CI where stdin is not a TTY and no SOPS_AGE_PASSPHRASE env var is set; a broken SOPS_AGE_PASSPHRASE_CMD script; piping commands so the prompt cannot read stdin.","solutions":["Set SOPS_AGE_PASSPHRASE (or your configured passphrase env var) in non-interactive environments.","Ensure a TTY is available or fix the SOPS_AGE_PASSPHRASE_CMD script so it exits 0 and prints the passphrase.","Store the identity unencrypted in CI (with file permissions 600) to avoid interactive prompts entirely."],"exampleFix":"// before (CI)\n- run: sops -d secrets.yaml\n// after\n- run: |\n    export SOPS_AGE_PASSPHRASE=\"$AGE_PASSPHRASE\"\n    sops -d secrets.yaml","handlingStrategy":"validation","validationCode":"if os.Getenv(\"SOPS_AGE_PASSPHRASE\") == \"\" && !term.IsTerminal(int(os.Stdin.Fd())) {\n    return fmt.Errorf(\"no TTY and SOPS_AGE_PASSPHRASE not set: passphrase unavailable\")\n}","typeGuard":null,"tryCatchPattern":"fileKey, err := identity.Unwrap(stanzas)\nif err != nil && strings.Contains(err.Error(), \"could not read passphrase\") {\n    // surface a clear CI hint: set SOPS_AGE_PASSPHRASE or provide a TTY\n}","preventionTips":["Always set SOPS_AGE_PASSPHRASE or a passphrase command in CI","Test passphrase-based decryption in a non-interactive shell before deploying","Prefer unencrypted key files with 600 permissions in automation"],"tags":["age","passphrase","non-interactive","ci","scrypt"],"backgroundTag":"passphrase-prompt-unavailable","analyzedSha":"13442bb98183887d7a9ac09ec8ab0564673a59d8","analyzedAt":"2026-09-01T03:53:00.447Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}