temporalio/temporal · info

%v, %w

Error message

%v, %w

What it means

appendError aggregates two errors: returns whichever one is nil, and when both are non-nil returns fmt.Errorf("%v, %v", aggregatedErr, err) — effectively joining them with ", ". The %w on the second error preserves the wrapped chain for errors.Is/As on the appended error. It is a helper for reporting accumulated cert-expiry/refresh problems, not a domain failure.

Source

Thrown at common/rpc/encryption/local_store_cert_provider.go:496

		if certDERBlock.Type == "CERTIFICATE" {
			certBytes = append(certBytes, certDERBlock.Bytes)
		}
	}

	if len(certBytes) == 0 || len(certBytes[0]) == 0 {
		return nil, fmt.Errorf("failed to decode PEM certificate data")
	}
	return x509.ParseCertificate(certBytes[0])
}

func appendError(aggregatedErr error, err error) error {
	if aggregatedErr == nil {
		return err
	}
	if err == nil {
		return aggregatedErr
	}
	return fmt.Errorf("%v, %w", aggregatedErr, err)
}

func (s *localStoreCertProvider) refreshCerts() {

	for {
		select {
		case <-s.stop:
			return
		case <-s.ticker.C:
		}

		newCerts, err := s.loadCerts()
		if err != nil {
			s.logger.Error("failed to load certificates", tag.Error(err))
			continue
		}

		s.RLock()

View on GitHub (pinned to bde624efd1)

Solutions

  1. Read the message as two comma-separated errors and address each underlying cause individually.
  2. Use errors.Is/errors.As against the second (wrapped) error to identify the root cause programmatically.
  3. Fix the primary (left-hand) error first; it is reported unwrapped via %v so the first error's chain is not preserved.

Example fix

// before
if err != nil { return err } // losing one of the joined errors
// after
if errors.Is(err, os.ErrNotExist) || strings.Contains(err.Error(), "failed to decode PEM") { /* handle underlying cause */ }
Defensive patterns

Strategy: type-guard

Type guard

func unwrapJoined(err error) []error {
    var out []error
    for err != nil {
        out = append(out, err)
        err = errors.Unwrap(err)
    }
    return out
}

Try / catch

if err != nil {
    var target error
    if errors.As(err, &target) { /* inspect joined/wrapped cause */ }
    log.Warn("aggregated cert errors", "detail", err.Error())
}

Prevention

When it happens

Trigger: Returned whenever both inputs are non-nil: GetExpiringCerts aggregating multiple certificate parsing/listing errors, or TestAppendError directly. Seeing this message text in output means at least two underlying errors were joined.

Common situations: A cert store with several expiring or corrupt certificates where the first error is the original message and the second is a subsequent failure during the same scan; reading the joined string and not realizing it contains two distinct problems.

Related errors


AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01). Data as JSON: /api/errors/aabf61dc2f6ec67c. Report an issue: GitHub.