sipeed/picoclaw · warning

port %d is out of range (1-65535)

Error message

port %d is out of range (1-65535)

What it means

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.

Source

Thrown at web/backend/api/launcher_config.go:92

		http.Error(w, fmt.Sprintf("Invalid JSON: %v", err), http.StatusBadRequest)
		return
	}

	cfg, err := h.loadLauncherConfig()
	if err != nil {
		http.Error(w, fmt.Sprintf("Failed to load launcher config: %v", err), http.StatusInternalServerError)
		return
	}
	cfg.Port = payload.Port
	cfg.Public = payload.Public
	cfg.AllowedCIDRs = append([]string(nil), payload.AllowedCIDRs...)
	if payload.AllowLocalhostBypass != nil {
		cfg.AllowLocalhostBypass = *payload.AllowLocalhostBypass
	}
	cfg.TrustedProxyCIDRs = append([]string(nil), payload.TrustedProxyCIDRs...)
	cfg.LegacyLauncherToken = ""
	if err := launcherconfig.Validate(cfg); err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}

	if err := launcherconfig.Save(h.launcherConfigPath(), cfg); err != nil {
		http.Error(w, fmt.Sprintf("Failed to save launcher config: %v", err), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	json.NewEncoder(w).Encode(launcherConfigPayload{
		Port:                 cfg.Port,
		Public:               cfg.Public,
		AllowedCIDRs:         append([]string(nil), cfg.AllowedCIDRs...),
		AllowLocalhostBypass: cfg.AllowLocalhostBypass,
		TrustedProxyCIDRs:    append([]string(nil), cfg.TrustedProxyCIDRs...),
	})
}

View on GitHub (pinned to 49183d7e8d)

Solutions

  1. Always include an integer port in 1-65535 in every PUT payload (e.g. 18800, the launcher default)
  2. If the intent was 'keep current port', first GET /api/system/launcher-config and echo the returned port back in the PUT
  3. Validate the range client-side before sending

Example fix

// before - port omitted, Go decodes 0, server rejects
PUT /api/system/launcher-config
{"public": true}

// after - full payload with valid port
PUT /api/system/launcher-config
{"port": 18800, "public": true, "allowed_cidrs": ["127.0.0.0/8"], "allow_localhost_bypass": true, "trusted_proxy_cidrs": []}
Defensive patterns

Strategy: validation

Validate before calling

if (!Number.isInteger(payload.port) || payload.port < 1 || payload.port > 65535) {
  throw new Error(`port ${payload.port} is out of range (1-65535)`);
}

Type guard

function hasValidPort(v: unknown): v is {port: number} {
  return typeof v === 'object' && v !== null &&
    Number.isInteger((v as {port?: unknown}).port) &&
    (v as {port: number}).port >= 1 && (v as {port: number}).port <= 65535;
}

Try / catch

const res = await fetch('/api/system/launcher-config', {method: 'PUT', ...});
if (res.status === 400) {
  const text = await res.text();
  if (text.includes('out of range (1-65535)')) {
    const cur = await (await fetch('/api/system/launcher-config')).json();
    payload.port = cur.port;              // echo back the current valid port
    return fetch('/api/system/launcher-config', {method: 'PUT', ...});  // retry with corrected payload
  }
  throw new Error(text);
}

Prevention

When it happens

Trigger: 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'.

Common situations: Clients modeled after PATCH semantics that only send changed fields; UI forms where the port input is left blank and serialized as 0.

Related errors


AI-assisted analysis of sipeed/picoclaw@49183d7e8d (2026-08-15). Data as JSON: /api/errors/905c48471a1a7777. Report an issue: GitHub.