{"record":{"id":"2b5e1b794860c80f","repo":"router-for-me/CLIProxyAPI","slug":"codex-live-media-relay-ice-servers-d-urls-is-req","errorCode":null,"errorMessage":"codex.live-media-relay.ice-servers[%d].urls is required","messagePattern":"codex\\.live-media-relay\\.ice-servers\\[(.+?)\\]\\.urls is required","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/config/codex_live.go","lineNumber":91,"sourceCode":"\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}\n\t\tfor _, rawURL := range server.URLs {\n\t\t\tparsed, errParse := url.Parse(strings.TrimSpace(rawURL))\n\t\t\tif errParse != nil || parsed.Scheme == \"\" {\n\t\t\t\treturn fmt.Errorf(\"codex.live-media-relay.ice-servers[%d] contains an invalid URL\", serverIndex)\n\t\t\t}\n\t\t\tswitch strings.ToLower(parsed.Scheme) {\n\t\t\tcase \"stun\", \"stuns\", \"turn\", \"turns\":\n\t\t\tdefault:\n\t\t\t\treturn fmt.Errorf(\"codex.live-media-relay.ice-servers[%d] uses unsupported scheme %q\", serverIndex, parsed.Scheme)\n\t\t\t}\n\t\t}\n\t}\n\treturn nil\n}\n","sourceCodeStart":73,"sourceCodeEnd":107,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/config/codex_live.go#L73-L107","documentation":"Every entry under codex.live-media-relay.ice-servers must list at least one URL; an entry with an empty or missing urls list fails validation at startup. The index in the message identifies which entry (0-based) is broken.","triggerScenarios":"Validate loops over c.ICEServers; server.URLs is empty — e.g. an ice-servers entry containing only username/credential, or a urls: [] empty list, or a key typo like 'url:' so the field never decodes.","commonSituations":"Copied TURN config snippet missing the urls line; YAML indent error placing urls outside the entry; commented-out urls while leaving the stub entry.","solutions":["Add urls to the offending entry: urls: [stun:stun.l.google.com:19302] or turn:... entries.","Remove the empty ice-servers entry entirely if unused.","Check indentation so urls nests under its list item, and verify the count of list items matches what you expect."],"exampleFix":"# before\nice-servers:\n  - username: user\n    credential: pass\n\n# after\nice-servers:\n  - urls:\n      - turn:turn.example.com:3478?transport=tcp\n    username: user\n    credential: pass","handlingStrategy":"validation","validationCode":"for i, s := range cfg.ICEServers {\n\tif len(s.URLs) == 0 {\n\t\treturn fmt.Errorf(\"ice-servers[%d] has no urls\", i)\n\t}\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Every ice-servers list item must contain a urls list.","Watch indentation — urls must be a sibling of username/credential under the same entry."],"tags":["config","validation","ice-servers","codex-live"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}