{"record":{"id":"905c48471a1a7777","repo":"sipeed/picoclaw","slug":"port-d-is-out-of-range-1-65535","errorCode":null,"errorMessage":"port %d is out of range (1-65535)","messagePattern":"port (.+?) is out of range \\(1-65535\\)","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"warning","filePath":"web/backend/api/launcher_config.go","lineNumber":92,"sourceCode":"\t\thttp.Error(w, fmt.Sprintf(\"Invalid JSON: %v\", err), http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tcfg, err := h.loadLauncherConfig()\n\tif err != nil {\n\t\thttp.Error(w, fmt.Sprintf(\"Failed to load launcher config: %v\", err), http.StatusInternalServerError)\n\t\treturn\n\t}\n\tcfg.Port = payload.Port\n\tcfg.Public = payload.Public\n\tcfg.AllowedCIDRs = append([]string(nil), payload.AllowedCIDRs...)\n\tif payload.AllowLocalhostBypass != nil {\n\t\tcfg.AllowLocalhostBypass = *payload.AllowLocalhostBypass\n\t}\n\tcfg.TrustedProxyCIDRs = append([]string(nil), payload.TrustedProxyCIDRs...)\n\tcfg.LegacyLauncherToken = \"\"\n\tif err := launcherconfig.Validate(cfg); err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tif err := launcherconfig.Save(h.launcherConfigPath(), cfg); err != nil {\n\t\thttp.Error(w, fmt.Sprintf(\"Failed to save launcher config: %v\", err), http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\tjson.NewEncoder(w).Encode(launcherConfigPayload{\n\t\tPort:                 cfg.Port,\n\t\tPublic:               cfg.Public,\n\t\tAllowedCIDRs:         append([]string(nil), cfg.AllowedCIDRs...),\n\t\tAllowLocalhostBypass: cfg.AllowLocalhostBypass,\n\t\tTrustedProxyCIDRs:    append([]string(nil), cfg.TrustedProxyCIDRs...),\n\t})\n}\n","sourceCodeStart":74,"sourceCodeEnd":110,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/web/backend/api/launcher_config.go#L74-L110","documentation":"Validation error (400) from PUT /api/system/launcher-config: launcherconfig.Validate (launcherconfig/config.go:52-54) rejected cfg.Port because it is below 1 or above 65535. The handler copies payload.Port verbatim, and Go zero-fills omitted fields - so omitting \"port\" yields 0 and this exact message ('port 0 is out of range (1-65535)'). The endpoint is a full replace, not a patch: every PUT must carry a valid port.","triggerScenarios":"Omitting port from the payload (defaults to 0); explicitly sending 0 or null; sending 65536+ after a mental math slip; copying a port from another config that uses 0 to mean 'ephemeral'.","commonSituations":"Clients modeled after PATCH semantics that only send changed fields; UI forms where the port input is left blank and serialized as 0.","solutions":["Always include an integer port in 1-65535 in every PUT payload (e.g. 18800, the launcher default)","If the intent was 'keep current port', first GET /api/system/launcher-config and echo the returned port back in the PUT","Validate the range client-side before sending"],"exampleFix":"// before - port omitted, Go decodes 0, server rejects\nPUT /api/system/launcher-config\n{\"public\": true}\n\n// after - full payload with valid port\nPUT /api/system/launcher-config\n{\"port\": 18800, \"public\": true, \"allowed_cidrs\": [\"127.0.0.0/8\"], \"allow_localhost_bypass\": true, \"trusted_proxy_cidrs\": []}","handlingStrategy":"validation","validationCode":"if (!Number.isInteger(payload.port) || payload.port < 1 || payload.port > 65535) {\n  throw new Error(`port ${payload.port} is out of range (1-65535)`);\n}","typeGuard":"function hasValidPort(v: unknown): v is {port: number} {\n  return typeof v === 'object' && v !== null &&\n    Number.isInteger((v as {port?: unknown}).port) &&\n    (v as {port: number}).port >= 1 && (v as {port: number}).port <= 65535;\n}","tryCatchPattern":"const res = await fetch('/api/system/launcher-config', {method: 'PUT', ...});\nif (res.status === 400) {\n  const text = await res.text();\n  if (text.includes('out of range (1-65535)')) {\n    const cur = await (await fetch('/api/system/launcher-config')).json();\n    payload.port = cur.port;              // echo back the current valid port\n    return fetch('/api/system/launcher-config', {method: 'PUT', ...});  // retry with corrected payload\n  }\n  throw new Error(text);\n}","preventionTips":["The PUT is a full replace: always include a valid integer port, never omit it","Prefer 1024-49151 registered range or the default 18800 for the launcher","Populate forms from GET first so the current port rides along in every save"],"tags":["validation","port","launcher","http","go"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}