VictoriaMetrics/VictoriaMetrics · error

cannot parse nodes: %w

Error message

cannot parse nodes: %w

What it means

parseNodes json.Unmarshals the /nodes response body into []node and wraps unmarshal failures with this error. The HTTP layer succeeded but the payload is not the expected JSON array of Swarm nodes. Typically a proxy-injected body or a schema/type mismatch between the daemon's output and the library's struct.

Source

Thrown at lib/promscrape/discovery/dockerswarm/nodes.go:76

	return addNodeLabels(nodes, cfg.port), nil
}

func getNodes(cfg *apiConfig) ([]node, error) {
	filtersQueryArg := ""
	if cfg.role == "nodes" {
		filtersQueryArg = cfg.filtersQueryArg
	}
	resp, err := cfg.getAPIResponse("/nodes", filtersQueryArg)
	if err != nil {
		return nil, fmt.Errorf("cannot query dockerswarm api for nodes: %w", err)
	}
	return parseNodes(resp)
}

func parseNodes(data []byte) ([]node, error) {
	var nodes []node
	if err := json.Unmarshal(data, &nodes); err != nil {
		return nil, fmt.Errorf("cannot parse nodes: %w", err)
	}
	return nodes, nil
}

func addNodeLabels(nodes []node, port int) []*promutil.Labels {
	var ms []*promutil.Labels
	for _, node := range nodes {
		m := promutil.NewLabels(16)
		m.Add("__address__", discoveryutil.JoinHostPort(node.Status.Addr, port))
		m.Add("__meta_dockerswarm_node_address", node.Status.Addr)
		m.Add("__meta_dockerswarm_node_availability", node.Spec.Availability)
		m.Add("__meta_dockerswarm_node_engine_version", node.Description.Engine.EngineVersion)
		m.Add("__meta_dockerswarm_node_hostname", node.Description.Hostname)
		m.Add("__meta_dockerswarm_node_id", node.ID)
		m.Add("__meta_dockerswarm_node_manager_address", node.ManagerStatus.Addr)
		m.Add("__meta_dockerswarm_node_manager_leader", fmt.Sprintf("%t", node.ManagerStatus.Leader))
		m.Add("__meta_dockerswarm_node_manager_reachability", node.ManagerStatus.Reachability)
		m.Add("__meta_dockerswarm_node_platform_architecture", node.Description.Platform.Architecture)

View on GitHub (pinned to 5079fb58f1)

Solutions

  1. Inspect the raw /nodes response body with the same credentials
  2. Ensure the response is a JSON array of node objects
  3. Test with an equivalent Docker API version to rule out schema drift
  4. Bypass intermediate proxies to rule out body substitution

Example fix

null
Defensive patterns

Strategy: type-guard

Validate before calling

// Go: reject non-array payloads before decoding into []node
if len(data) == 0 || data[0] != '[' {
    return fmt.Errorf("/nodes returned non-array payload: %.200s", data)
}

Type guard

// Go: narrow via a lightweight probe decode
func isNodeArray(data []byte) bool {
    var probe []json.RawMessage
    return json.Unmarshal(data, &probe) == nil
}

Try / catch

null

Prevention

When it happens

Trigger: json.Unmarshal(data, &nodes) errors: body is an HTML error page, empty, or a JSON object instead of an array; a field type changed in a newer Docker engine version.

Common situations: MitM proxy or SSO gateway returning HTML; Docker Engine upgrade changing node JSON types; daemon returning an error object {"message":...} with a 200 that then fails unmarshal into an array.

Understand the failure class

Background: "failed to unmarshal" / json.Unmarshal errors: why parsing a response into a Go struct fails and how to fix it — this error's family across 23 libraries.

Related errors


AI-assisted analysis of VictoriaMetrics/VictoriaMetrics@5079fb58f1 (2026-09-03). Data as JSON: /api/errors/56427390d538354d. Report an issue: GitHub.