hashicorp/consul · error

unable to convert %T to proto

Error message

unable to convert %T to proto

What it means

ConfigEntryFromStructs (proto/private/pbconfigentry/config_entry.go:264 region) panics when the concrete Go type of the structs.ConfigEntry interface value is not one of the handled cases (e.g. *structs.MeshConfigEntry, *structs.ServiceResolverConfigEntry, ... *structs.ExportedServicesConfigEntry). Unlike a missing enum case, this is a Go type-switch exhaustiveness problem: any wrapper/decorator implementing the interface, any newly added entry type, and a nil interface all fall through. The %T in the message tells you exactly which concrete type was rejected.

Source

Thrown at proto/private/pbconfigentry/config_entry.go:264

		}
	case *structs.JWTProviderConfigEntry:
		var jwtProvider JWTProvider
		JWTProviderFromStructs(v, &jwtProvider)

		configEntry.Kind = Kind_KindJWTProvider
		configEntry.Entry = &ConfigEntry_JWTProvider{
			JWTProvider: &jwtProvider,
		}
	case *structs.ExportedServicesConfigEntry:
		var es ExportedServices
		ExportedServicesFromStructs(v, &es)

		configEntry.Kind = Kind_KindExportedServices
		configEntry.Entry = &ConfigEntry_ExportedServices{
			ExportedServices: &es,
		}
	default:
		panic(fmt.Sprintf("unable to convert %T to proto", s))
	}

	return configEntry
}

func tlsVersionToStructs(s string) types.TLSVersion {
	return types.TLSVersion(s)
}

func tlsVersionFromStructs(t types.TLSVersion) string {
	return t.String()
}

func cipherSuitesToStructs(cs []string) []types.TLSCipherSuite {
	cipherSuites := make([]types.TLSCipherSuite, len(cs))
	for idx, suite := range cs {
		cipherSuites[idx] = types.TLSCipherSuite(suite)
	}

View on GitHub (pinned to 2397ff0d76)

Solutions

  1. Add the missing case to the type switch in ConfigEntryFromStructs (and the enum case in ConfigEntryToStructs)
  2. Unwrap decorated/wrapped entries to their underlying concrete type before calling the converter
  3. Nil-check the entry before conversion
  4. Keep mixed-version clusters aligned so all binaries know the same entry types

Example fix

// before
pbEntry := pbconfigentry.ConfigEntryFromStructs(wrappedEntry) // wrapper type -> panic

// after
switch e := wrappedEntry.(type) {
case *structs.ServiceDefaultsConfigEntry, *structs.MeshConfigEntry:
	pbEntry = pbconfigentry.ConfigEntryFromStructs(e)
default:
	return fmt.Errorf("unsupported config entry type %T", e)
}
Defensive patterns

Strategy: type-guard

Validate before calling

func registeredConfigEntry(s structs.ConfigEntry) bool {
	switch s.(type) {
	case *structs.MeshConfigEntry, *structs.ServiceResolverConfigEntry, *structs.IngressGatewayConfigEntry,
		*structs.ServiceIntentionsConfigEntry, *structs.ServiceConfigEntry, *structs.APIGatewayConfigEntry,
		*structs.BoundAPIGatewayConfigEntry, *structs.TCPRouteConfigEntry, *structs.HTTPRouteConfigEntry,
		*structs.FileSystemCertificateConfigEntry, *structs.InlineCertificateConfigEntry,
		*structs.SamenessGroupConfigEntry, *structs.JWTProviderConfigEntry, *structs.ExportedServicesConfigEntry:
		return true
	}
	return false
}

if s == nil || !registeredConfigEntry(s) {
	return fmt.Errorf("unsupported config entry type %T", s)
}
pbEntry := pbconfigentry.ConfigEntryFromStructs(s)

Type guard

func asRegisteredEntry(s structs.ConfigEntry) (structs.ConfigEntry, bool) {
	if s == nil {
		return nil, false
	}
	if w, ok := s.(interface{ Unwrap() structs.ConfigEntry }); ok {
		s = w.Unwrap() // peel wrappers/decorators
	}
	return s, registeredConfigEntry(s)
}

Try / catch

defer func() {
	if r := recover(); r != nil {
		err = fmt.Errorf("config entry serialization failed for %T: %v", entry, r)
	}
}()

Prevention

When it happens

Trigger: Passing a new structs.*ConfigEntry kind that has no case in the switch; passing a wrapper or test double that implements structs.ConfigEntry around a real entry; passing a nil structs.ConfigEntry; enterprise/CE divergence where one side has types the other lacks.

Common situations: Consul development adding a new config entry kind without updating both converters; wrapping entries for validation/mutation and forgetting to unwrap before serialization; test helpers that build mock ConfigEntry implementations.

Related errors


AI-assisted analysis of hashicorp/consul@2397ff0d76 (2026-08-15). Data as JSON: /api/errors/e50858ed21506302. Report an issue: GitHub.