XTLS/Xray-core · error
outbound metadata not found
Error message
outbound metadata not found
What it means
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.
Source
Thrown at app/reverse/portal.go:71
}, nil
}
func (p *Portal) Start() error {
return p.ohm.AddHandler(context.Background(), &Outbound{
portal: p,
tag: p.tag,
})
}
func (p *Portal) Close() error {
return p.ohm.RemoveHandler(context.Background(), p.tag)
}
func (p *Portal) HandleConnection(ctx context.Context, link *transport.Link) error {
outbounds := session.OutboundsFromContext(ctx)
ob := outbounds[len(outbounds)-1]
if ob == nil {
return errors.New("outbound metadata not found").AtError()
}
if isDomain(ob.Target, p.domain) {
muxClient, err := mux.NewClientWorker(*link, mux.ClientStrategy{})
if err != nil {
return errors.New("failed to create mux client worker").Base(err).AtWarning()
}
worker, err := NewPortalWorker(muxClient)
if err != nil {
return errors.New("failed to create portal worker").Base(err)
}
p.picker.AddWorker(worker)
if _, ok := link.Reader.(*pipe.Reader); !ok {
select {
case <-ctx.Done():View on GitHub (pinned to 7d214f8b09)
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.
Example fix
// before (custom integration)
ctx := session.ContextWithOutbounds(ctx, []*session.Outbound{nil}) // -> hits invariant
// after
ob := &session.Outbound{ Target: net.TCPDestination(net.DomainAddress("svc.reverse.internal"), 80) }
ctx := session.ContextWithOutbounds(ctx, []*session.Outbound{ob}) Defensive patterns
Strategy: type-guard
Validate before calling
// Build the context properly before dispatching to the portal tag
if len(session.OutboundsFromContext(ctx)) == 0 {
ob := &session.Outbound{Target: dest}
ctx = session.ContextWithOutbounds(ctx, []*session.Outbound{ob})
} Type guard
func hasTerminalOutbound(ctx context.Context) bool {
obs := session.OutboundsFromContext(ctx)
return len(obs) > 0 && obs[len(obs)-1] != nil
} Try / catch
// Defensive guard at integration boundaries
if err := portal.HandleConnection(ctx, link); err != nil {
if strings.Contains(err.Error(), "outbound metadata not found") {
ctx = session.ContextWithOutbounds(ctx, []*session.Outbound{{Target: dest}})
err = portal.HandleConnection(ctx, link)
}
return err
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- bridge tag is empty
- bridge domain is empty
- portal tag is empty
- portal domain is empty
- failed to create mux client worker
AI-assisted analysis of XTLS/Xray-core@7d214f8b09 (2026-08-15).
Data as JSON: /api/errors/6eff1ecb6811aaeb.
Report an issue: GitHub.