JuliusBrussee/caveman · error
invalid listen port %q
Error message
invalid listen port %q
What it means
Returned by runstate.PortFromListen when the listen string split into host:port but the port portion is not a decimal integer in 1-65535. The run-state filename is derived from the port, so it must be a real TCP port number.
Source
Thrown at proxy/internal/runstate/runstate.go:59
PID int `json:"pid,omitempty"`
Port int `json:"port,omitempty"`
StartedAt time.Time `json:"started_at,omitempty"`
Version string `json:"version,omitempty"`
RecoveryViaMCP bool `json:"recovery_via_mcp"`
}
func Unknown() PublicState {
return PublicState{Owner: "unknown"}
}
func PortFromListen(listen string) (int, error) {
_, raw, err := net.SplitHostPort(listen)
if err != nil {
return 0, fmt.Errorf("invalid listen address %q: %w", listen, err)
}
port, err := strconv.Atoi(raw)
if err != nil || port < 1 || port > 65535 {
return 0, fmt.Errorf("invalid listen port %q", raw)
}
return port, nil
}
func Path(home string, port int) string {
return filepath.Join(home, "run", strconv.Itoa(port)+".json")
}
func New(listen, mode, owner, version string) (State, error) {
port, err := PortFromListen(listen)
if err != nil {
return State{}, err
}
if owner != "wrap" && owner != "start" {
owner = "start"
}
var token [16]byte
if _, err := rand.Read(token[:]); err != nil {View on GitHub (pinned to 27d5a3981a)
Solutions
- Use a concrete port number between 1 and 65535, e.g. "127.0.0.1:8397"
- If you wanted an ephemeral port, resolve it first (net.Listen, get the bound port) and pass the resolved address
- Fix typos and ensure env-derived port variables are set and numeric
- Lint the config at startup: strconv.Atoi + range check before runstate.New
Example fix
// before
listen := "127.0.0.1:0" // hope for random port
// after
l, _ := net.Listen("tcp", "127.0.0.1:0")
port := l.Addr().(*net.TCPAddr).Port
listen := fmt.Sprintf("127.0.0.1:%d", port) Defensive patterns
Strategy: validation
Validate before calling
func normalizePort(listen string) (string, error) {
_, raw, err := net.SplitHostPort(listen)
if err != nil { return "", err }
p, err := strconv.Atoi(raw)
if err != nil || p < 1 || p > 65535 {
return "", fmt.Errorf("port %q must be 1-65535", raw)
}
return fmt.Sprintf("127.0.0.1:%d", p), nil
} Type guard
func isValidPort(s string) bool {
_, raw, err := net.SplitHostPort(s)
if err != nil { return false }
p, err := strconv.Atoi(raw)
return err == nil && p >= 1 && p <= 65535
} Try / catch
_, err := runstate.New(listen, mode, owner, version)
if err != nil && strings.Contains(err.Error(), "invalid listen port") {
// reject config early with a clear message instead of half-starting the server
} Prevention
- Never pass port 0 or named ports into runstate; resolve ephemeral ports first
- Range-check numeric ports in config validation (1-65535)
- Unit-test config parsing with bad ports: 0, 70000, 'http', '8o80'
When it happens
Trigger: Passing a listen string like "localhost:http" (service name instead of number), "localhost:0", "localhost:70000", or "localhost:8o80" (typo letter) into runstate.New or PortFromListen.
Common situations: Copy-paste of a named port from documentation or nginx-style configs; port 0 chosen for 'random port' semantics which runstate rejects; a typo in a config file (letter O for zero); a port computed from an env var that is unset and rendered as garbage.
Related errors
- invalid listen address %q: %w
- generic target %q requires a non-empty source path
- cave_budget_denomination_ambiguous
- cave_budget_max_invalid
- cave_budget_output_floor_invalid
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/7cea634a85354ca3.
Report an issue: GitHub.