{"record":{"id":"6eff1ecb6811aaeb","repo":"XTLS/Xray-core","slug":"outbound-metadata-not-found","errorCode":null,"errorMessage":"outbound metadata not found","messagePattern":"outbound metadata not found","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/reverse/portal.go","lineNumber":71,"sourceCode":"\t}, nil\n}\n\nfunc (p *Portal) Start() error {\n\treturn p.ohm.AddHandler(context.Background(), &Outbound{\n\t\tportal: p,\n\t\ttag:    p.tag,\n\t})\n}\n\nfunc (p *Portal) Close() error {\n\treturn p.ohm.RemoveHandler(context.Background(), p.tag)\n}\n\nfunc (p *Portal) HandleConnection(ctx context.Context, link *transport.Link) error {\n\toutbounds := session.OutboundsFromContext(ctx)\n\tob := outbounds[len(outbounds)-1]\n\tif ob == nil {\n\t\treturn errors.New(\"outbound metadata not found\").AtError()\n\t}\n\n\tif isDomain(ob.Target, p.domain) {\n\t\tmuxClient, err := mux.NewClientWorker(*link, mux.ClientStrategy{})\n\t\tif err != nil {\n\t\t\treturn errors.New(\"failed to create mux client worker\").Base(err).AtWarning()\n\t\t}\n\n\t\tworker, err := NewPortalWorker(muxClient)\n\t\tif err != nil {\n\t\t\treturn errors.New(\"failed to create portal worker\").Base(err)\n\t\t}\n\n\t\tp.picker.AddWorker(worker)\n\n\t\tif _, ok := link.Reader.(*pipe.Reader); !ok {\n\t\t\tselect {\n\t\t\tcase <-ctx.Done():","sourceCodeStart":53,"sourceCodeEnd":89,"githubUrl":"https://github.com/XTLS/Xray-core/blob/7d214f8b094f75322fa3990f8aadad1c912f24f5/app/reverse/portal.go#L53-L89","documentation":"Internal invariant failure in Portal.HandleConnection: it pops the last element of the session's outbound chain (OutboundsFromContext) to inspect the connection target, and that element is nil. This should not happen in normal flows because every dispatched connection carries at least one outbound record; encountering it means the caller built a context without proper outbound metadata.","triggerScenarios":"Calling Portal.HandleConnection (directly or by dispatching to the portal's outbound tag) with a context that has no valid terminal session.Outbound — e.g. a custom integration constructing ctx via session.ContextWithOutbounds with a nil last element, or a code path that appended a nil outbound. The nil check fires before any tunnel logic runs.","commonSituations":"Almost exclusively a bug in custom code embedding xray-core that dispatches to the reverse portal tag with a malformed session context; stock configs never produce it. Also possible after refactors that changed where outbounds are appended.","solutions":["Audit the custom dispatcher: ensure every Dispatch call carries a context built with session.ContextWithOutbounds and a fully populated, non-nil last outbound (Target set).","Reproduce with loglevel debug and print the outbound chain length before dispatching to the portal tag.","If using stock xray, update to the latest core release in case a regression introduced a path skipping outbound append."],"exampleFix":"// before (custom integration)\nctx := session.ContextWithOutbounds(ctx, []*session.Outbound{nil}) // -> hits invariant\n\n// after\nob := &session.Outbound{ Target: net.TCPDestination(net.DomainAddress(\"svc.reverse.internal\"), 80) }\nctx := session.ContextWithOutbounds(ctx, []*session.Outbound{ob})","handlingStrategy":"type-guard","validationCode":"// Build the context properly before dispatching to the portal tag\nif len(session.OutboundsFromContext(ctx)) == 0 {\n    ob := &session.Outbound{Target: dest}\n    ctx = session.ContextWithOutbounds(ctx, []*session.Outbound{ob})\n}","typeGuard":"func hasTerminalOutbound(ctx context.Context) bool {\n    obs := session.OutboundsFromContext(ctx)\n    return len(obs) > 0 && obs[len(obs)-1] != nil\n}","tryCatchPattern":"// Defensive guard at integration boundaries\nif err := portal.HandleConnection(ctx, link); err != nil {\n    if strings.Contains(err.Error(), \"outbound metadata not found\") {\n        ctx = session.ContextWithOutbounds(ctx, []*session.Outbound{{Target: dest}})\n        err = portal.HandleConnection(ctx, link)\n    }\n    return err\n}","preventionTips":["Always dispatch through the standard dispatcher, never call HandleConnection with a hand-built ctx","When embedding xray-core, wrap dispatch helpers that enforce outbound metadata invariants","Add a unit test asserting the outbound chain is non-nil at every dispatch site"],"tags":["go","xray","reverse-proxy","session","internal"],"backgroundTag":null,"analyzedSha":"7d214f8b094f75322fa3990f8aadad1c912f24f5","analyzedAt":"2026-08-15T14:26:24.325Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}