{"record":{"id":"68c6d29f1f3cbbba","repo":"istio/istio","slug":"failed-to-unmarshal-s-v","errorCode":null,"errorMessage":"failed to unmarshal %s: %v","messagePattern":"failed to unmarshal (.+?): (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pilot/cmd/pilot-agent/status/server.go","lineNumber":275,"sourceCode":"\t\tregistry:              registry,\n\t\tshutdown:              config.Shutdown,\n\t\tdrain:                 config.TriggerDrain,\n\t\tdisableDrain:          config.DisableDrain,\n\t\tmaxAppBodyBytes:       defaultMaxAppMetricsBodyBytes,\n\t}\n\tif LegacyLocalhostProbeDestination.Get() {\n\t\ts.appProbersDestination = \"localhost\"\n\t}\n\n\t// Enable prometheus server if its configured and a sidecar\n\t// Because port 15020 is exposed in the gateway Services, we cannot safely serve this endpoint\n\t// If we need to do this in the future, we should use envoy to do routing or have another port to make this internal\n\t// only. For now, its not needed for gateway, as we can just get Envoy stats directly, but if we\n\t// want to expose istio-agent metrics we may want to revisit this.\n\tif cfg, f := PrometheusScrapingConfig.Lookup(); config.NodeType == model.SidecarProxy && f {\n\t\tvar prom PrometheusScrapeConfiguration\n\t\tif err := json.Unmarshal([]byte(cfg), &prom); err != nil {\n\t\t\treturn nil, fmt.Errorf(\"failed to unmarshal %s: %v\", PrometheusScrapingConfig.Name, err)\n\t\t}\n\t\tlog.Infof(\"Prometheus scraping configuration: %v\", prom)\n\t\tif prom.Scrape != \"false\" {\n\t\t\ts.prometheus = &prom\n\t\t\tif len(s.prometheus.Targets) == 0 {\n\t\t\t\t// Legacy path: apply defaults and synthesize a single Targets entry.\n\t\t\t\tif s.prometheus.Path == \"\" {\n\t\t\t\t\ts.prometheus.Path = \"/metrics\"\n\t\t\t\t}\n\t\t\t\tif s.prometheus.Port == \"\" {\n\t\t\t\t\ts.prometheus.Port = \"80\"\n\t\t\t\t}\n\t\t\t\tif s.prometheus.Port == strconv.Itoa(int(config.StatusPort)) {\n\t\t\t\t\treturn nil, fmt.Errorf(\"invalid prometheus scrape configuration: \"+\n\t\t\t\t\t\t\"application port is the same as agent port, which may lead to a recursive loop. \"+\n\t\t\t\t\t\t\"Ensure pod does not have prometheus.io/port=%d label, or that injection is not happening multiple times\", config.StatusPort)\n\t\t\t\t}\n\t\t\t\ts.prometheus.Targets = []ScrapeTarget{{Port: s.prometheus.Port, Path: s.prometheus.Path}}","sourceCodeStart":257,"sourceCodeEnd":293,"githubUrl":"https://github.com/istio/istio/blob/8dc789c5cf17517c64e3c36cb3288230f149dfae/pilot/cmd/pilot-agent/status/server.go#L257-L293","documentation":"NewServer failed to json.Unmarshal the PrometheusScrapingConfig setting (looked up via PrometheusScrapingConfig.Lookup()) into the PrometheusScrapeConfiguration struct. This setting controls application metrics merging on port 15020 and is only parsed for SidecarProxy nodes. The value is a JSON string; any syntax error or wrong field type aborts agent startup.","triggerScenarios":"PrometheusScrapingConfig is set (annotation/mesh/proxyConfig surface) and node type is SidecarProxy, but the string is not valid JSON for {scrape, path, port, targets} — e.g. trailing commas, single quotes, YAML instead of JSON, or port given as a number instead of a string.","commonSituations":"Hand-writing the scrape config JSON in an annotation or meshConfig and quoting it wrong; passing YAML because the surrounding config file is YAML; version upgrade that changed the expected schema (e.g. introduction of the targets field); copy-paste from docs that mangled quotes.","solutions":["Check the wrapped json error in the log — it gives the exact offset/field that failed","Validate the value with a JSON linter and against the PrometheusScrapeConfiguration shape: scrape/path/port are strings, targets is an array of {port, path} strings","Fix quoting: in YAML annotations use the '>' or '|' block style or escape double quotes so the delivered value is one JSON document","If you do not need app-metrics merging, set scrape to \"false\" (still valid JSON) or remove the config entirely"],"exampleFix":"# before\nannotations:\n  prometheus.istio.io/merge-metrics-config: '{ scrape: true, port: 8080, }'  # YAML-ish, trailing comma\n# after\nannotations:\n  prometheus.istio.io/merge-metrics-config: |\n    {\"scrape\": \"true\", \"targets\": [{\"port\": \"8080\", \"path\": \"/metrics\"}]}","handlingStrategy":"validation","validationCode":"// Validate the scrape config JSON exactly like the agent before injecting/starting\nvar prom PrometheusScrapeConfiguration\nif err := json.Unmarshal([]byte(cfgString), &prom); err != nil {\n    return fmt.Errorf(\"bad PrometheusScrapingConfig, fix before rollout: %w\", err)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Run every hand-edited scrape config through a JSON schema/linter in CI","Write JSON in annotations with YAML block scalars (|) to avoid quote mangling","Remember ports and paths are strings in this struct, not numbers"],"tags":["istio","prometheus","json","configuration","sidecar","validation"],"backgroundTag":null,"analyzedSha":"8dc789c5cf17517c64e3c36cb3288230f149dfae","analyzedAt":"2026-08-15T15:16:55.434Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}