{"record":{"id":"8d8bc40ac8e5ca6c","repo":"XTLS/Xray-core","slug":"failed-to-produce-report","errorCode":null,"errorMessage":"failed to produce report","messagePattern":"failed to produce report","errorType":"console","errorClass":"errors.Error","httpStatus":null,"severity":"warning","filePath":"app/observatory/explainErrors.go","lineNumber":23,"sourceCode":"type errorCollector struct {\n\terrors *errors.Error\n}\n\nfunc (e *errorCollector) SubmitError(err error) {\n\tif e.errors == nil {\n\t\te.errors = errors.New(\"underlying connection error\").Base(err)\n\t\treturn\n\t}\n\te.errors = e.errors.Base(errors.New(\"underlying connection error\").Base(err))\n}\n\nfunc newErrorCollector() *errorCollector {\n\treturn &errorCollector{}\n}\n\nfunc (e *errorCollector) UnderlyingError() error {\n\tif e.errors == nil {\n\t\treturn errors.New(\"failed to produce report\")\n\t}\n\treturn e.errors\n}\n","sourceCodeStart":5,"sourceCodeEnd":27,"githubUrl":"https://github.com/XTLS/Xray-core/blob/7d214f8b094f75322fa3990f8aadad1c912f24f5/app/observatory/explainErrors.go#L5-L27","documentation":"Returned by errorCollector.UnderlyingError() in app/observatory/explainErrors.go:23 when the probe's HTTP request failed but no connection-level error was ever recorded in the collector. That means the failure happened at a layer Xray's session tracking does not instrument (for example TLS handshake timeout, HTTP 5xx-style transport error, or response-read failure), so there is no underlying connection report to show.","triggerScenarios":"The HTTP client's Do() returns an error while DialContext either succeeded or failed before any tracked connection error was submitted (e.g. TLSHandshakeTimeout of 5s exceeded, or context deadline). The code then asks errorCollectorForRequest.UnderlyingError() and gets this placeholder.","commonSituations":"Observatory logs where an outbound is reported dead 'with outbound handler report underlying connection failed' but the explanation is just 'failed to produce report' — typical for MITM'd networks, broken TLS to the probe URL, or probes killed by the 5-second client Timeout.","solutions":["Treat this as 'no connection-level diagnosis available' and debug at the transport layer: try the probe URL manually with curl from the outbound host.","Set observatory ProbeUrl to a plain-HTTP-reachable or more reliable endpoint (default is https://www.google.com/generate_204) — in blocked regions Google probes fail without a clear connection error.","Check whether the outbound's TLS/streamSettings are valid; handshake failures often bypass connection tracking.","Increase client timeout context if probes are slow but alive."],"exampleFix":"// before\n\"observatory\": {\"subjectSelector\": [\"out\"]}\n\n// after — probe a reachable URL to get meaningful diagnostics\n\"observatory\": {\n  \"subjectSelector\": [\"out\"],\n  \"probeURL\": \"https://www.gstatic.com/generate_204\"\n}","handlingStrategy":"validation","validationCode":"// choose a probe URL known-reachable through your outbounds before relying on diagnostics\nif observatoryCfg.ProbeUrl == \"\" && inCensoredRegion() {\n    observatoryCfg.ProbeUrl = \"https://www.gstatic.com/generate_204\"\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Set an explicit ProbeUrl reachable from all outbounds.","When 'failed to produce report' appears, debug at TLS/transport level — connection tracking saw nothing."],"tags":["go","xray","observatory","diagnostics","tls"],"backgroundTag":null,"analyzedSha":"7d214f8b094f75322fa3990f8aadad1c912f24f5","analyzedAt":"2026-08-15T14:26:24.325Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}