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
- Upgrade the older binaries so server and agent share the same set of kinds
- If developing a new kind, add the case to ConfigEntryToStructs (and the mirror case to ConfigEntryFromStructs)
- Validate Kind against the known set and return an error at the RPC/decode boundary before converting
- 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
- Reject unknown kinds at the RPC/decode boundary instead of letting the converter see them
- Always set Kind when building proto ConfigEntry values; zero-value Kind is not valid
- When adding a Kind enum value, update both converters and the kind-validation set in the same commit
- Avoid mixed agent/server versions during rolling upgrades once new kinds exist
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
- unable to convert %T to proto
- unknown placement %d
- client streams are unsupported
- ClusterSize not set
- failed to decode request: %v
AI-assisted analysis of hashicorp/consul@2397ff0d76 (2026-08-15).
Data as JSON: /api/errors/6bd0478637577aa9.
Report an issue: GitHub.