hashicorp/consul · error

unable to convert ConfigEntry of kind %s to structs

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.

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)

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.