grpc/grpc-go · error

recovered from panic during resource parsing, resource

Error message

recovered from panic during resource parsing, resource: %v, panic: %v

What it means

Produced by a recover() guard in xdsChannel.decodeResponse (channel.go:260), enabled only when GRPC_XDS_RECOVER_PANIC_IN_RESOURCE_PARSING (envconfig.XDSRecoverPanicInResourceParsing) is on. If a ResourceType.Decoder implementation panics while decoding a resource, the panic is converted to this error and the resource is treated as a NACKed top-level error rather than crashing the process.

Solutions

  1. Inspect the panic value and stack in the logs - it identifies the decoder and line that panicked.
  2. Fix the Decoder implementation to handle malformed input without panicking (return an error instead).
  3. If the decoder is third-party, upgrade or pin a version that handles the resource safely.
  4. Keep XDS_RECOVER_PANIC_IN_RESOURCE_PARSING enabled in production so a single bad resource cannot crash the client.
Defensive patterns

Strategy: try-catch

Try / catch

// The library already wraps the panic as an error. In a custom Decoder:
defer func() {
    if r := recover(); r != nil {
        err = fmt.Errorf("decoder panic: %v", r)
    }
}()

Prevention

When it happens

Trigger: rType.Decoder.Decode(NewAnyProto(r), *opts) panics - e.g. a custom ResourceType decoder hits a nil pointer, indexes out of range, or asserts a bad type. The deferred recover catches it and returns the formatted error, which then lands in topLevelErrors and causes the response to be NACKed.

Common situations: Custom ResourceType implementation with a bug; a malformed Any payload triggers an unchecked assumption in a vendored decoder; version mismatch where the decoder receives a proto it was not built for.

Related errors


AI-assisted analysis of grpc/grpc-go@0c51461d27 (2026-08-11). Data as JSON: /api/errors/c6e9bc0caaea7b05. Report an issue: GitHub.

Appendix: source

Thrown at internal/xds/clients/xdsclient/channel.go:260

	timestamp := time.Now()
	md := xdsresource.UpdateMetadata{
		Version:   resp.version,
		Timestamp: timestamp,
	}
	opts := &DecodeOptions{
		Config:       xc.clientConfig,
		ServerConfig: xc.serverConfig,
	}

	topLevelErrors := make([]error, 0)          // Tracks deserialization errors, where we don't have a resource name.
	perResourceErrors := make(map[string]error) // Tracks resource validation errors, where we have a resource name.
	ret := make(map[string]dataAndErrTuple)     // Return result, a map from resource name to either resource data or error.
	for _, r := range resp.resources {
		result, err := func() (res *DecodeResult, err error) {
			defer func() {
				if envconfig.XDSRecoverPanicInResourceParsing {
					if p := recover(); p != nil {
						err = fmt.Errorf("recovered from panic during resource parsing, resource: %v, panic: %v", r, p)
					}
				}
			}()
			return rType.Decoder.Decode(NewAnyProto(r), *opts)
		}()

		// Name field of the result is left unpopulated only when resource
		// deserialization fails.
		name := ""
		if result == nil && err == nil {
			xc.logger.Errorf("Decode() returned nil result and nil error for resource: %v", r)
			continue
		}
		if result != nil {
			name = xdsresource.ParseName(result.Name).String()
		}
		if err == nil {
			ret[name] = dataAndErrTuple{Resource: result.Resource}

View on GitHub (pinned to 0c51461d27)