{"record":{"id":"c398bca7290d673e","repo":"MHSanaei/3x-ui","slug":"address-is-required","errorCode":null,"errorMessage":"address is required","messagePattern":"address is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/util/netsafe/netsafe.go","lineNumber":68,"sourceCode":"\t\t}\n\t\tconn, derr := defaultDialer.DialContext(ctx, network, net.JoinHostPort(ipAddr.IP.String(), port))\n\t\tif derr == nil {\n\t\t\treturn conn, nil\n\t\t}\n\t\tlastErr = derr\n\t}\n\tif lastErr == nil {\n\t\tlastErr = fmt.Errorf(\"no usable address for %s\", host)\n\t}\n\treturn nil, lastErr\n}\n\nvar hostnamePattern = regexp.MustCompile(`^[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?(\\.[A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?)*$`)\n\nfunc NormalizeHost(addr string) (string, error) {\n\taddr = strings.TrimSpace(addr)\n\tif addr == \"\" {\n\t\treturn \"\", fmt.Errorf(\"address is required\")\n\t}\n\tif strings.HasPrefix(addr, \"[\") && strings.HasSuffix(addr, \"]\") {\n\t\taddr = addr[1 : len(addr)-1]\n\t}\n\tif ip := net.ParseIP(addr); ip != nil {\n\t\treturn ip.String(), nil\n\t}\n\tif len(addr) > 253 || !hostnamePattern.MatchString(addr) {\n\t\treturn \"\", fmt.Errorf(\"invalid host %q\", addr)\n\t}\n\treturn addr, nil\n}\n","sourceCodeStart":50,"sourceCodeEnd":81,"githubUrl":"https://github.com/MHSanaei/3x-ui/blob/ad32144c42455696ea9f14e12168beac3e25f5d2/internal/util/netsafe/netsafe.go#L50-L81","documentation":"NormalizeHost rejects an address that is empty after TrimSpace with 'address is required'. It is the first validation in the normalization pipeline (strip brackets → parse IP → validate hostname pattern), so it fires before any other host error. Any caller (e.g. Remote.baseURL in the node runtime) that feeds it an unset field hits this immediately.","triggerScenarios":"Calling NormalizeHost(\"\"), NormalizeHost(\"   \"), or passing a struct field that was never populated — e.g. a node/sub-node record saved with an empty Address column, then used to build a base URL.","commonSituations":"A node added to the panel with the address left blank; a form/UI bug not marking address required; an API client POSTing a node JSON without the address key; whitespace-only value pasted from clipboard.","solutions":["Set the address field on the offending record (node settings, subscription config) to a hostname or IP.","If building an API client, include the address key in the create/update payload and validate client-side.","Trim user input before saving so whitespace-only values are caught at entry.","Find the caller from the stack trace to identify which record has the empty address."],"exampleFix":"// before\naddr := node.Address // \"\" -> address is required\nhost, err := netsafe.NormalizeHost(addr)\n\n// after\naddr := strings.TrimSpace(node.Address)\nif addr == \"\" {\n    return fmt.Errorf(\"node %d has no address\", node.Id)\n}\nhost, err := netsafe.NormalizeHost(addr)","handlingStrategy":"validation","validationCode":"if strings.TrimSpace(node.Address) == \"\" {\n    return fmt.Errorf(\"node address is required\")\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Make address a required field in node forms and API payloads.","Validate on save, not on first use in the runtime path.","Add a DB check constraint or a startup audit for empty addresses."],"tags":["validation","configuration","nodes"],"backgroundTag":null,"analyzedSha":"ad32144c42455696ea9f14e12168beac3e25f5d2","analyzedAt":"2026-08-15T11:13:23.905Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}