{"record":{"id":"4e7ab8f1d2253358","repo":"router-for-me/CLIProxyAPI","slug":"codex-live-media-relay-public-ip-is-invalid-q","errorCode":null,"errorMessage":"codex.live-media-relay.public-ip is invalid: %q","messagePattern":"codex\\.live-media-relay\\.public-ip is invalid: %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/config/codex_live.go","lineNumber":74,"sourceCode":"\n// EffectiveMaxSessions returns the configured media session limit.\nfunc (c CodexLiveMediaRelayConfig) EffectiveMaxSessions() int {\n\tif c.MaxSessions > 0 {\n\t\treturn c.MaxSessions\n\t}\n\treturn DefaultCodexLiveMediaMaxSessions\n}\n\n// Validate verifies the Codex Live media relay configuration.\nfunc (c CodexLiveMediaRelayConfig) Validate() error {\n\tif !c.Enabled {\n\t\treturn nil\n\t}\n\tif c.MaxSessions < 0 {\n\t\treturn errors.New(\"codex.live-media-relay.max-sessions must not be negative\")\n\t}\n\tif publicIP := strings.TrimSpace(c.PublicIP); publicIP != \"\" && net.ParseIP(publicIP) == nil {\n\t\treturn fmt.Errorf(\"codex.live-media-relay.public-ip is invalid: %q\", publicIP)\n\t}\n\tif (c.UDPPortMin == 0) != (c.UDPPortMax == 0) {\n\t\treturn errors.New(\"codex.live-media-relay UDP port minimum and maximum must both be set\")\n\t}\n\tif c.UDPPortMin > c.UDPPortMax {\n\t\treturn errors.New(\"codex.live-media-relay.udp-port-min must not exceed udp-port-max\")\n\t}\n\tif c.UDPPortMin != 0 {\n\t\tavailablePorts := int(c.UDPPortMax) - int(c.UDPPortMin) + 1\n\t\trequiredPorts := c.EffectiveMaxSessions() * 2\n\t\tif availablePorts < requiredPorts {\n\t\t\treturn fmt.Errorf(\"codex.live-media-relay UDP range requires at least %d ports for %d sessions\", requiredPorts, c.EffectiveMaxSessions())\n\t\t}\n\t}\n\tfor serverIndex, server := range c.ICEServers {\n\t\tif len(server.URLs) == 0 {\n\t\t\treturn fmt.Errorf(\"codex.live-media-relay.ice-servers[%d].urls is required\", serverIndex)\n\t\t}","sourceCodeStart":56,"sourceCodeEnd":92,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/config/codex_live.go#L56-L92","documentation":"Config validation: when the Codex live media relay is enabled, an explicitly set public-ip must parse as an IP address (net.ParseIP). This value is advertised in SDP so remote peers can reach the relay; anything unparseable is rejected at startup.","triggerScenarios":"CodexLiveMediaRelayConfig.Validate() runs from LoadConfigOptional after YAML decode; c.PublicIP is non-empty after trimming but net.ParseIP returns nil — e.g. a hostname like 'media.example.com', or a typo like '192.168.1.256'.","commonSituations":"Users put a DNS name where a literal IP is required; trailing CIDR notation ('203.0.113.5/32'); trailing whitespace is trimmed but internal junk is not.","solutions":["Set public-ip to a literal IPv4/IPv6 address: public-ip: 203.0.113.5.","If you only have a hostname, resolve it first and put the resulting IP in the config (the relay does not resolve DNS itself).","Remove CIDR prefixes or zone identifiers; only bare addresses pass net.ParseIP."],"exampleFix":"# before\npublic-ip: media.example.com\n\n# after\npublic-ip: 203.0.113.5","handlingStrategy":"validation","validationCode":"if ip := net.ParseIP(strings.TrimSpace(cfg.PublicIP)); ip == nil {\n\treturn fmt.Errorf(\"public-ip %q is not a literal IP address\", cfg.PublicIP)\n}","typeGuard":"func isValidPublicIP(s string) bool { return net.ParseIP(strings.TrimSpace(s)) != nil }","tryCatchPattern":null,"preventionTips":["DNS names are not resolved — always configure a literal IP.","If your IP is dynamic, update the config (hot-reload supported) when it changes."],"tags":["config","validation","ip-address","codex-live","media-relay"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}