helm/helm · error
unable to read key file: %q: %w
Error message
unable to read key file: %q: %w
What it means
The key-file counterpart of the cert read in WithCertKeyPairFiles: after the cert file reads successfully, os.ReadFile is called on keyFile and any failure is wrapped with the quoted path. The same both-empty guard applies, so setting only the key path makes the option fail here (or earlier on the empty cert path). The wrapped error carries the concrete OS cause.
Source
Thrown at internal/tlsutil/tls.go:56
return nil
}
}
func WithCertKeyPairFiles(certFile, keyFile string) TLSConfigOption {
return func(options *TLSConfigOptions) error {
if certFile == "" && keyFile == "" {
return nil
}
certPEMBlock, err := os.ReadFile(certFile)
if err != nil {
return fmt.Errorf("unable to read cert file: %q: %w", certFile, err)
}
keyPEMBlock, err := os.ReadFile(keyFile)
if err != nil {
return fmt.Errorf("unable to read key file: %q: %w", keyFile, err)
}
options.certPEMBlock = certPEMBlock
options.keyPEMBlock = keyPEMBlock
return nil
}
}
func WithCAFile(caFile string) TLSConfigOption {
return func(options *TLSConfigOptions) error {
if caFile == "" {
return nil
}
caPEMBlock, err := os.ReadFile(caFile)
if err != nil {
return fmt.Errorf("can't read CA file: %q: %w", caFile, err)View on GitHub (pinned to 2a29f1770b)
Solutions
- Verify the quoted path exists and is readable: ls -l <path>
- Supply both cert and key paths together — one without the other is rejected
- Prefer absolute paths for credentials
- Correct ownership/permissions (e.g., chown/chmod 600 for the key) when the cause is access-related
Example fix
# before cfg, err := tlsutil.NewTLSConfig(tlsutil.WithCertKeyPairFiles(certPath, "")) # after cfg, err := tlsutil.NewTLSConfig(tlsutil.WithCertKeyPairFiles(certPath, keyPath))
Defensive patterns
Strategy: validation
Validate before calling
func validateTLSFilePair(certFile, keyFile string) error {
if (certFile == "") != (keyFile == "") {
return fmt.Errorf("cert and key files must be provided together")
}
for _, f := range []string{certFile, keyFile} {
if f == "" {
return nil
}
if _, err := os.Stat(f); err != nil {
return fmt.Errorf("stat %s: %w", f, err)
}
}
return nil
} Try / catch
cfg, err := tlsutil.NewTLSConfig(tlsutil.WithCertKeyPairFiles(cert, key))
if err != nil {
var pathErr *fs.PathError
if errors.As(err, &pathErr) && errors.Is(pathErr, fs.ErrPermission) {
// key file present but unreadable by this user: fix ownership/permissions
}
return err
} Prevention
- Never set only one of the pair — the option requires both or neither
- Store key files with 600 permissions owned by the Helm process user
- Validate both paths with os.Stat in a preflight check before starting operations
When it happens
Trigger: NewTLSConfig with WithCertKeyPairFiles where keyFile does not exist, is a directory, or is unreadable, or where keyFile is "" while certFile is set. Relative key paths resolved from an unexpected working directory also land here.
Common situations: Key file held in a secret not mounted at the expected path; key flagged 600 and owned by another user; only --key-file given on the command line; path with a typo or trailing whitespace from a shell variable.
Related errors
- unable to read cert file: %q: %w
- can't read CA file: %q: %w
- can't create TLS config for client: %w
- can't create TLS config for client: %w
- unable to load cert from key pair: %w
AI-assisted analysis of helm/helm@2a29f1770b (2026-08-15).
Data as JSON: /api/errors/f72652f6993f9436.
Report an issue: GitHub.