{"record":{"id":"e5bc0b698e7d3200","repo":"XTLS/Xray-core","slug":"underlying-connection-error","errorCode":null,"errorMessage":"underlying connection error","messagePattern":"underlying connection error","errorType":"console","errorClass":"errors.Error","httpStatus":null,"severity":"warning","filePath":"app/observatory/explainErrors.go","lineNumber":11,"sourceCode":"package observatory\n\nimport \"github.com/xtls/xray-core/common/errors\"\n\ntype 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":1,"sourceCodeEnd":27,"githubUrl":"https://github.com/XTLS/Xray-core/blob/7d214f8b094f75322fa3990f8aadad1c912f24f5/app/observatory/explainErrors.go#L1-L27","documentation":"Created in app/observatory/explainErrors.go:11 by errorCollector.SubmitError: it is the wrapper label prepended to the first underlying connection error recorded during an observatory probe. Xray's session system tracks connection errors through the dispatch path, and this collector chains them so the final probe report can explain why a connection died.","triggerScenarios":"Any outbound connection attempt made while probing an outbound (via session.TrackedConnectionError contexts) reports an error back to the collector — e.g. dial failure, TLS failure, or an upstream proxy refusing the connection. This wrapper becomes the head of that error chain.","commonSituations":"Almost always seen inside 'the outbound X is dead' log lines from the observatory; the useful information is the chained inner error (dial timeout, SOCKS rejection, TLS alert), not this wrapper text itself.","solutions":["Read the chained error (Base) — run err with %v in a multi-error-aware logger or inspect errors.Error path — to find the true cause.","Fix the underlying outbound: check server reachability, credentials, and TLS settings of the proxy being probed.","If the probe target itself is unreachable from the server, set probeUrl in the observatory config to a host you can reach."],"exampleFix":null,"handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"// consumers of observatory results: this is a warning-chain wrapper, not a failure to handle\nif result.Alive { keepOutbound() } else { logChainedErrors(result.LastErrorReason) }","preventionTips":["Log the full Base chain so the wrapper never hides the root cause.","Keep observatory probe targets reachable to reduce connection-error noise."],"tags":["go","xray","observatory","error-chaining","diagnostics"],"backgroundTag":null,"analyzedSha":"7d214f8b094f75322fa3990f8aadad1c912f24f5","analyzedAt":"2026-08-15T14:26:24.325Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}