{"record":{"id":"cdaf5f08c4c409b7","repo":"istio/istio","slug":"invalid-prometheus-scrape-configuration-target-po","errorCode":null,"errorMessage":"invalid prometheus scrape configuration: target port %s is the same as agent port, which may lead to a recursive loop","messagePattern":"invalid prometheus scrape configuration: target port (.+?) is the same as agent port, which may lead to a recursive loop","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pilot/cmd/pilot-agent/status/server.go","lineNumber":316,"sourceCode":"\t\t\t\t\ts.prometheus.Port = s.prometheus.Targets[0].Port\n\t\t\t\t}\n\t\t\t\tif s.prometheus.Path == \"\" {\n\t\t\t\t\ts.prometheus.Path = s.prometheus.Targets[0].Path\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Default path and validate every target port numerically to catch leading-zero\n\t\t\t// representations (e.g. \"015020\" dials 15020 at runtime but != \"15020\" as a string).\n\t\t\tfor i, t := range s.prometheus.Targets {\n\t\t\t\tif t.Path == \"\" {\n\t\t\t\t\ts.prometheus.Targets[i].Path = \"/metrics\"\n\t\t\t\t}\n\t\t\t\tportNum, atoiErr := strconv.Atoi(t.Port)\n\t\t\t\tif atoiErr != nil || portNum < 1 || portNum > 65535 {\n\t\t\t\t\treturn nil, fmt.Errorf(\"invalid prometheus scrape configuration: \"+\n\t\t\t\t\t\t\"invalid target port %q\", t.Port)\n\t\t\t\t}\n\t\t\t\tif portNum == int(config.StatusPort) {\n\t\t\t\t\treturn nil, fmt.Errorf(\"invalid prometheus scrape configuration: \"+\n\t\t\t\t\t\t\"target port %s is the same as agent port, which may lead to a recursive loop\", t.Port)\n\t\t\t\t}\n\t\t\t\tif reason, reserved := IstioReservedPortReason(strconv.Itoa(portNum)); reserved {\n\t\t\t\t\treturn nil, fmt.Errorf(\"invalid prometheus scrape configuration: \"+\n\t\t\t\t\t\t\"target port %s is reserved for Istio (%s) and cannot be scraped\", t.Port, reason)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif config.KubeAppProbers == \"\" {\n\t\treturn s, nil\n\t}\n\tif err := json.Unmarshal([]byte(config.KubeAppProbers), &s.appKubeProbers); err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to decode app prober err = %v, json string = %v\", err, config.KubeAppProbers)\n\t}\n\n\ts.appProbeClient = make(map[string]*http.Client, len(s.appKubeProbers))","sourceCodeStart":298,"sourceCodeEnd":334,"githubUrl":"https://github.com/istio/istio/blob/8dc789c5cf17517c64e3c36cb3288230f149dfae/pilot/cmd/pilot-agent/status/server.go#L298-L334","documentation":"New-format validation in NewServer: a targets[] entry's numeric port equals the agent status port (config.StatusPort, default 15020). The agent would scrape its own merged /metrics endpoint — which already contains that data — producing an infinite recursive scrape loop, so startup is rejected. This is the multi-target analogue of the legacy-path check, comparing values numerically so \"015020\" is also caught.","triggerScenarios":"PrometheusScrapingConfig with scrape != false and a targets array where some target's port Atoi-parses to exactly config.StatusPort — e.g. {\"port\":\"15020\"} or {\"port\":\"015020\"}.","commonSituations":"Copying the merged port 15020 into the new multi-target list out of habit from the prometheus.io/port annotation; statusPort customized via flags to a value someone also used for an app metrics port; leading-zero ports sneaking through templating.","solutions":["Remove or re-point the offending target (the error names the port) to the application's real metrics port","Never include 15020 (or your customized status port) in targets — Envoy metrics are already merged automatically","Normalize ports in your config pipeline to canonical decimal form (strip leading zeros) so string and numeric views agree","Validate targets with IstioReservedPortReason before applying; it also flags this class of mistake"],"exampleFix":"// before\n{\"scrape\":\"true\",\"targets\":[{\"port\":\"8080\"},{\"port\":\"15020\"}]}\n// after\n{\"scrape\":\"true\",\"targets\":[{\"port\":\"8080\"},{\"port\":\"9090\",\"path\":\"/actuator/prometheus\"}]}","handlingStrategy":"validation","validationCode":"statusPort := 15020 // config.StatusPort used by the agent\nfor _, t := range cfg.Targets {\n    if n, _ := strconv.Atoi(t.Port); n == statusPort {\n        return fmt.Errorf(\"target %s scrapes the agent itself (recursion)\", t.Port)\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Envoy metrics are auto-merged — never add 15020 or the status port as a target","Normalize away leading zeros when templating ports (\"015020\" == 15020 numerically)"],"tags":["istio","prometheus","validation","configuration","ports","recursion"],"backgroundTag":null,"analyzedSha":"8dc789c5cf17517c64e3c36cb3288230f149dfae","analyzedAt":"2026-08-15T15:16:55.434Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}