{"record":{"id":"d3290cb588df457b","repo":"XTLS/Xray-core","slug":"empty-worker-list","errorCode":null,"errorMessage":"empty worker list","messagePattern":"empty worker list","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/reverse/portal.go","lineNumber":180,"sourceCode":"\t\t\tactiveWorkers = append(activeWorkers, w)\n\t\t} else {\n\t\t\tw.timer.SetTimeout(0)\n\t\t}\n\t}\n\n\tif len(activeWorkers) != len(p.workers) {\n\t\tp.workers = activeWorkers\n\t}\n\n\treturn nil\n}\n\nfunc (p *StaticMuxPicker) PickAvailable() (*mux.ClientWorker, error) {\n\tp.access.Lock()\n\tdefer p.access.Unlock()\n\n\tif len(p.workers) == 0 {\n\t\treturn nil, errors.New(\"empty worker list\")\n\t}\n\n\tvar minIdx int = -1\n\tvar minConn uint32 = 9999\n\tfor i, w := range p.workers {\n\t\tif w.draining {\n\t\t\tcontinue\n\t\t}\n\t\tif w.IsFull() {\n\t\t\tcontinue\n\t\t}\n\t\tif w.client.ActiveConnections() < minConn {\n\t\t\tminConn = w.client.ActiveConnections()\n\t\t\tminIdx = i\n\t\t}\n\t}\n\n\tif minIdx == -1 {","sourceCodeStart":162,"sourceCodeEnd":198,"githubUrl":"https://github.com/XTLS/Xray-core/blob/7d214f8b094f75322fa3990f8aadad1c912f24f5/app/reverse/portal.go#L162-L198","documentation":"StaticMuxPicker (the portal's pool of bridge-backed mux workers) has zero registered workers when PickAvailable is called. Every tunneled connection must ride an existing worker; with an empty list there is no bridge connected at all, so picking fails. Typically returned up through the outbound dispatcher when traffic is routed to the portal tag but no bridge has established its tunnel.","triggerScenarios":"Traffic dispatched to the portal's outbound tag while p.workers == 0 — i.e. before any bridge connected, or after all bridge workers were reaped (closed/drained and removed by the picker's cleanup that filters activeWorkers).","commonSituations":"Portal side up but bridge side down/offline (reverse client never connected, or lost network); bridge connects to the wrong portal address; startup ordering where routing sends traffic before the first bridge tunnel arrives; long outages where the last worker was cleaned up.","solutions":["Verify the bridge side is running and its outbound points at the portal's address/tag; check portal logs for accepted bridge connections.","Ensure routing rules send traffic to the portal tag only when bridges exist, or add a fallbackTag/observability so the failure is visible.","If workers keep disappearing, debug why bridge connections drop (see errors 91/92) — this error is usually the symptom, not the cause."],"exampleFix":null,"handlingStrategy":"fallback","validationCode":"// Before routing traffic to the portal tag, confirm bridges are connected\nif picker.Len() == 0 { // expose worker count from StaticMuxPicker\n    return routeDirectOrReject(ctx, link) // no bridge tunnel exists yet\n}","typeGuard":"func portalHasWorkers(picker reverse.WorkerCounter) bool { return picker.Len() > 0 }","tryCatchPattern":"if err := dispatch(ctx, link); err != nil {\n    if strings.Contains(err.Error(), \"empty worker list\") {\n        return routeViaFallbackTag(ctx, link) // or queue briefly until first bridge registers\n    }\n    return err\n}","preventionTips":["Start bridges before routing production traffic to the portal tag","Monitor bridge connection state and alert when worker count stays zero","Add a routing fallback (different balancer/outbound) for portal-tag rules"],"tags":["go","xray","reverse-proxy","mux","availability"],"backgroundTag":null,"analyzedSha":"7d214f8b094f75322fa3990f8aadad1c912f24f5","analyzedAt":"2026-08-15T14:26:24.325Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}