go-task/task · error

failed to read CA certificate: %w

Error message

failed to read CA certificate: %w

What it means

When --cacert is supplied, buildHTTPClient reads the CA bundle from disk with os.ReadFile. Any read failure (missing file, permission denied, path is a directory) is wrapped as "failed to read CA certificate" with the OS error.

Source

Thrown at taskfile/node_http.go:48

	// Validate that cert and certKey are provided together
	if (cert != "" && certKey == "") || (cert == "" && certKey != "") {
		return nil, fmt.Errorf("both --cert and --cert-key must be provided together")
	}

	// If no TLS customization is needed, return the default client
	if !insecure && caCert == "" && cert == "" {
		return http.DefaultClient, nil
	}

	tlsConfig := &tls.Config{
		InsecureSkipVerify: insecure, //nolint:gosec
	}

	// Load custom CA certificate if provided
	if caCert != "" {
		caCertData, err := os.ReadFile(caCert)
		if err != nil {
			return nil, fmt.Errorf("failed to read CA certificate: %w", err)
		}
		caCertPool := x509.NewCertPool()
		if !caCertPool.AppendCertsFromPEM(caCertData) {
			return nil, fmt.Errorf("failed to parse CA certificate")
		}
		tlsConfig.RootCAs = caCertPool
	}

	// Load client certificate and key if provided
	if cert != "" && certKey != "" {
		clientCert, err := tls.LoadX509KeyPair(cert, certKey)
		if err != nil {
			return nil, fmt.Errorf("failed to load client certificate: %w", err)
		}
		tlsConfig.Certificates = []tls.Certificate{clientCert}
	}

	return &http.Client{

View on GitHub (pinned to 385e5ad92a)

Solutions

  1. Fix the --cacert path to point at an existing PEM file the user can read
  2. Ensure the CA bundle exists in the container/CI image (copy it in or install ca-certificates)
  3. Check permissions: chmod 644 the CA file if needed
  4. Look at the wrapped OS error in the message to distinguish not-found vs permission vs is-a-directory

Example fix

# before
task --cacert /etc/ssl/certs -f https://internal.example.com/Taskfile.yml
# after
task --cacert /etc/ssl/certs/corp-root.pem -f https://internal.example.com/Taskfile.yml
Defensive patterns

Strategy: validation

Validate before calling

func caCertReadable(path string) error {
    fi, err := os.Stat(path)
    if err != nil {
        return fmt.Errorf("cacert %s: %w", path, err)
    }
    if !fi.Mode().IsRegular() {
        return fmt.Errorf("cacert %s is not a regular file", path)
    }
    f, err := os.Open(path)
    if err != nil {
        return fmt.Errorf("cacert %s unreadable: %w", path, err)
    }
    f.Close()
    return nil
}

Type guard

func isReadablePEMFile(path string) bool {
    fi, err := os.Stat(path)
    return err == nil && fi.Mode().IsRegular() && fi.Mode().Perm()&0o400 != 0
}

Try / catch

node, err := taskfile.NewHTTPNode(..., caCert, ...)
if err != nil {
    var pe *fs.PathError
    if errors.As(err, &pe) && strings.Contains(err.Error(), "failed to read CA certificate") {
        // correct path/permissions before retrying
    }
    return err
}

Prevention

When it happens

Trigger: NewHTTPNode -> buildHTTPClient with a non-empty caCert string that os.ReadFile cannot read: nonexistent path, unreadable permissions, or a directory passed as the cert file.

Common situations: Pointing --cacert at a directory (like the system /etc/ssl/certs) instead of a PEM bundle file; typo in the cert path; container images missing the corporate CA bundle; permission-restricted certs in CI.

Understand the failure class

Related errors


AI-assisted analysis of go-task/task@385e5ad92a (2026-09-05). Data as JSON: /api/errors/2fcc55e417a4e948. Report an issue: GitHub.