hashicorp/consul · error

unable to convert ConfigEntry of kind

Error message

unable to convert ConfigEntry of kind %s to structs

What it means

ConfigEntryToStructs (proto/private/pbconfigentry/config_entry.go) panics when the proto ConfigEntry's Kind does not match one of the handled cases (meshconfig, service-resolver, ingress-gateway, service-intentions, api-gateway, bound-api-gateway, tcp-route, http-route, file-system-certificate, inline-certificate, service-defaults, sameness-group, jwt-provider, exported-services). The zero value Kind_KindUnset also falls through to the panic. In a mixed-version cluster this is typically version skew: a newer agent/client sends a kind this binary has never heard of, and the internal RPC path has no graceful fallback for unknown kinds.

Solutions

  1. Upgrade the older binaries so server and agent share the same set of kinds
  2. If developing a new kind, add the case to ConfigEntryToStructs (and the mirror case to ConfigEntryFromStructs)
  3. Validate Kind against the known set and return an error at the RPC/decode boundary before converting
  4. When building proto entries in code or fixtures, always set Kind alongside Entry

Example fix

// before
out := pbconfigentry.ConfigEntryToStructs(entry) // entry.Kind from a newer agent, or unset

// after
if !knownConfigEntryKind(entry.GetKind()) {
	return nil, fmt.Errorf("unsupported config entry kind: %s", entry.GetKind())
}
out := pbconfigentry.ConfigEntryToStructs(entry)
Defensive patterns

Strategy: validation

Validate before calling

var knownConfigEntryKinds = map[pbconfigentry.Kind]bool{
	pbconfigentry.Kind_KindMeshConfig: true, pbconfigentry.Kind_KindServiceResolver: true,
	pbconfigentry.Kind_KindIngressGateway: true, pbconfigentry.Kind_KindServiceIntentions: true,
	pbconfigentry.Kind_KindAPIGateway: true, pbconfigentry.Kind_KindBoundAPIGateway: true,
	pbconfigentry.Kind_KindTCPRoute: true, pbconfigentry.Kind_KindHTTPRoute: true,
	pbconfigentry.Kind_KindFileSystemCertificate: true, pbconfigentry.Kind_KindInlineCertificate: true,
	pbconfigentry.Kind_KindServiceDefaults: true, pbconfigentry.Kind_KindSamenessGroup: true,
	pbconfigentry.Kind_KindJWTProvider: true, pbconfigentry.Kind_KindExportedServices: true,
}

if !knownConfigEntryKinds[entry.GetKind()] {
	return nil, fmt.Errorf("unsupported config entry kind: %s", entry.GetKind())
}
out := pbconfigentry.ConfigEntryToStructs(entry)

Try / catch

// at an RPC handler boundary, translate the panic into an error response
defer func() {
	if r := recover(); r != nil {
		err = fmt.Errorf("cannot convert config entry (version skew?): %v", r)
	}
}()

Prevention

When it happens

Trigger: An older server converts a ConfigEntry sent by a newer agent whose proto enum contains a new kind; constructing a proto ConfigEntry without setting Kind (zero value Kind_KindUnset) and passing it to the converter; during development, adding a Kind enum value but forgetting the corresponding case in ConfigEntryToStructs.

Common situations: Rolling upgrades of Consul where agents and servers temporarily run different versions; test fixtures with hand-built proto entries missing the Kind field; contributors adding a new config entry kind who updated the enum but not this converter.

Related errors


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

Appendix: source

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

		return &target
	case Kind_KindJWTProvider:
		var target structs.JWTProviderConfigEntry
		target.Name = s.Name

		JWTProviderToStructs(s.GetJWTProvider(), &target)
		pbcommon.RaftIndexToStructs(s.RaftIndex, &target.RaftIndex)
		pbcommon.EnterpriseMetaToStructs(s.EnterpriseMeta, &target.EnterpriseMeta)
		return &target
	case Kind_KindExportedServices:
		var target structs.ExportedServicesConfigEntry
		target.Name = s.Name

		ExportedServicesToStructs(s.GetExportedServices(), &target)
		pbcommon.RaftIndexToStructs(s.RaftIndex, &target.RaftIndex)
		pbcommon.EnterpriseMetaToStructs(s.EnterpriseMeta, &target.EnterpriseMeta)
		return &target
	default:
		panic(fmt.Sprintf("unable to convert ConfigEntry of kind %s to structs", s.Kind))
	}
}

func ConfigEntryFromStructs(s structs.ConfigEntry) *ConfigEntry {
	configEntry := &ConfigEntry{
		Name:           s.GetName(),
		EnterpriseMeta: pbcommon.NewEnterpriseMetaFromStructs(*s.GetEnterpriseMeta()),
	}

	var raftIndex pbcommon.RaftIndex
	pbcommon.RaftIndexFromStructs(s.GetRaftIndex(), &raftIndex)
	configEntry.RaftIndex = &raftIndex

	switch v := s.(type) {
	case *structs.MeshConfigEntry:
		var meshConfig MeshConfig
		MeshConfigFromStructs(v, &meshConfig)

View on GitHub (pinned to 2397ff0d76)