JuliusBrussee/caveman · error
%s (%s): trailing PEM block is truncated after %d certificat
Error message
%s (%s): trailing PEM block is truncated after %d certificate(s), so the bundle is incomplete and must not be half-trusted
What it means
Thrown by rootsWithCAFile (chhttp.go:188) after the PEM decode loop ends: the leftover bytes still contain a '-----BEGIN' marker, meaning the file ends with a PEM header whose block was never completed. A truncated trailing certificate would be silently dropped, producing a half-trusted pool, so the loader refuses the whole bundle instead. The count of successfully added certificates is included.
Source
Thrown at shared/platform/chhttp/chhttp.go:188
rest := bundle
for {
var block *pem.Block
block, rest = pem.Decode(rest)
if block == nil {
break
}
if block.Type != "CERTIFICATE" {
continue
}
cert, err := x509.ParseCertificate(block.Bytes)
if err != nil {
return nil, fmt.Errorf("%s (%s): certificate %d is unparseable, so the bundle is incomplete and must not be half-trusted: %w", caFileEnv, path, added+1, err)
}
roots.AddCert(cert)
added++
}
if bytes.Contains(rest, []byte("-----BEGIN")) {
return nil, fmt.Errorf("%s (%s): trailing PEM block is truncated after %d certificate(s), so the bundle is incomplete and must not be half-trusted", caFileEnv, path, added)
}
if added == 0 {
return nil, fmt.Errorf("%s (%s) contains no valid PEM certificate", caFileEnv, path)
}
return roots, nil
}
// errTransport fails every request with the configuration error that produced
// it. A client constructor cannot return an error, and falling back to the
// default transport would silently trade a rejected TLS configuration for
// unpinned verification — so the client is built, and refuses to send.
type errTransport struct{ err error }
func (t errTransport) RoundTrip(*http.Request) (*http.Response, error) { return nil, t.err }
// queryTransport is the shared connection pool behind ClickHouse query clients.
//
// http.DefaultTransport keeps only DefaultMaxIdleConnsPerHost (2) idleView on GitHub (pinned to 27d5a3981a)
Solutions
- Check the tail of the bundle: the last block must end with a complete -----END CERTIFICATE----- line followed by a newline.
- Re-download or regenerate the bundle from the source CA and compare checksums.
- Strip the truncated trailing block and re-test, or restore from a known-good copy.
- Add a CI check that validates the bundle end-to-end (openssl verifies every block) before deployment.
Example fix
# before: truncated tail -----BEGIN CERTIFICATE----- MIIB...(cut off mid-bas # after: complete block -----BEGIN CERTIFICATE----- MIIB...full base64... -----END CERTIFICATE-----
Defensive patterns
Strategy: validation
Validate before calling
// after decoding all blocks, leftover '-----BEGIN' means truncation
rest := data
for {
var blk *pem.Block
blk, rest = pem.Decode(rest)
if blk == nil { break }
}
if bytes.Contains(rest, []byte("-----BEGIN")) {
return fmt.Errorf("bundle truncated: trailing PEM header without complete block")
} Try / catch
err := validateBundle(path)
if err != nil {
log.Fatalf("config: %v", err) // treat as fatal: refuse partial trust
} Prevention
- Write bundles atomically (temp file + rename) so readers never see partial writes.
- Checksum downloads and compare against the source.
- End every PEM file with a newline after the END line.
When it happens
Trigger: The CA-file bundle's final PEM block is missing its END line, has a broken base64 body, or was cut off mid-write - pem.Decode returns nil for it, the loop breaks, and the remaining bytes still contain '-----BEGIN'.
Common situations: File truncated by a partial download or a full disk during generation; editor save that dropped the last lines; cat of two files where the second was still being written; a bundle piped through a command whose output was limited (head, log truncation).
Understand the failure class
- SSL/TLS and certificate errors — how TLS handshakes and certificate validation fail.
Related errors
- %s (%s): certificate %d is unparseable, so the bundle is inc
- %s (%s) contains no valid PEM certificate
- %s: load system certificate pool: %w
- production requires an https:// CLICKHOUSE_URL (TLS only); C
- githubapp: private key is not valid PEM
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/a3f101cc3145d6e2.
Report an issue: GitHub.