thanos-io/thanos · error

expected 4 components in config metadata, and received

Error message

expected 4 components in config metadata, and received %d, meta: %s

What it means

After reading the first line of the memcached cluster CONFIG response, parseConfig splits it on spaces and expects exactly 4 components ('CONFIG cluster 0 <size>'). If the metadata line has a different number of space-separated tokens, the response does not conform to the protocol and this error is returned with the offending line embedded.

Solutions

  1. Confirm the endpoint is a cluster-mode ElastiCache / memcached cluster answering 'CONFIG cluster ...'.
  2. Inspect the raw first line (included in the error message) to identify the unexpected response.
  3. Remove proxies or middlewares that alter the protocol response.
  4. Fix test mocks to emit exactly 'CONFIG cluster 0 <len>\r\n'.

Example fix

// before
// server replies: "ERROR unsupported command" -> 2 components
// after: validate endpoint mode first
if !isClusterModeEndpoint(addr) {
    return nil, errors.New("memcached auto-discovery requires a cluster-mode endpoint")
}
Defensive patterns

Strategy: validation

Validate before calling

func looksLikeConfigMeta(line string) bool {
    parts := strings.Fields(line)
    return len(parts) == 4 && parts[0] == "CONFIG" && parts[1] == "cluster"
}

Try / catch

cfg, err := discovery.parseConfig(reader)
if err != nil && strings.Contains(err.Error(), "expected 4 components") {
    return nil, fmt.Errorf("endpoint %s is not answering the cluster CONFIG protocol: %w", addr, err)
}

Prevention

When it happens

Trigger: The server's first response line is not 'CONFIG cluster 0 <len>' — e.g. an ERROR/CLIENT_ERROR reply from a non-cluster memcached, a truncated/corrupt response, or extra/missing tokens from a protocol-incompatible endpoint.

Common situations: Connecting to plain memcached (returns 'ERROR' to 'config get'), ElastiCache in non-cluster (replica) mode, or a custom/mock server emitting malformed metadata.

Related errors


AI-assisted analysis of thanos-io/thanos@35b8b99117 (2026-09-07). Data as JSON: /api/errors/5bb6eee6adf3eaee. Report an issue: GitHub.

Appendix: source

Thrown at pkg/discovery/memcache/resolver.go:72

		return nil, err
	}

	return config, err
}

func (s *memcachedAutoDiscovery) parseConfig(reader *bufio.Reader) (*clusterConfig, error) {
	clusterConfig := new(clusterConfig)

	configMeta, err := reader.ReadString('\n')
	if err != nil {
		return nil, fmt.Errorf("failed to read config metadata: %s", err)
	}
	configMeta = strings.TrimSpace(configMeta)

	// First line should be "CONFIG cluster 0 [length-of-payload-]
	configMetaComponents := strings.Split(configMeta, " ")
	if len(configMetaComponents) != 4 {
		return nil, fmt.Errorf("expected 4 components in config metadata, and received %d, meta: %s", len(configMetaComponents), configMeta)
	}

	configSize, err := strconv.Atoi(configMetaComponents[3])
	if err != nil {
		return nil, fmt.Errorf("failed to parse config size from metadata: %s, error: %s", configMeta, err)
	}

	configVersion, err := reader.ReadString('\n')
	if err != nil {
		return nil, fmt.Errorf("failed to find config version: %s", err)
	}
	clusterConfig.version, err = strconv.Atoi(strings.TrimSpace(configVersion))
	if err != nil {
		return nil, fmt.Errorf("failed to parser config version: %s", err)
	}

	nodes, err := reader.ReadString('\n')
	if err != nil {

View on GitHub (pinned to 35b8b99117)