{"record":{"id":"7cea634a85354ca3","repo":"JuliusBrussee/caveman","slug":"invalid-listen-port-q","errorCode":null,"errorMessage":"invalid listen port %q","messagePattern":"invalid listen port %q","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"proxy/internal/runstate/runstate.go","lineNumber":59,"sourceCode":"\tPID            int       `json:\"pid,omitempty\"`\n\tPort           int       `json:\"port,omitempty\"`\n\tStartedAt      time.Time `json:\"started_at,omitempty\"`\n\tVersion        string    `json:\"version,omitempty\"`\n\tRecoveryViaMCP bool      `json:\"recovery_via_mcp\"`\n}\n\nfunc Unknown() PublicState {\n\treturn PublicState{Owner: \"unknown\"}\n}\n\nfunc PortFromListen(listen string) (int, error) {\n\t_, raw, err := net.SplitHostPort(listen)\n\tif err != nil {\n\t\treturn 0, fmt.Errorf(\"invalid listen address %q: %w\", listen, err)\n\t}\n\tport, err := strconv.Atoi(raw)\n\tif err != nil || port < 1 || port > 65535 {\n\t\treturn 0, fmt.Errorf(\"invalid listen port %q\", raw)\n\t}\n\treturn port, nil\n}\n\nfunc Path(home string, port int) string {\n\treturn filepath.Join(home, \"run\", strconv.Itoa(port)+\".json\")\n}\n\nfunc New(listen, mode, owner, version string) (State, error) {\n\tport, err := PortFromListen(listen)\n\tif err != nil {\n\t\treturn State{}, err\n\t}\n\tif owner != \"wrap\" && owner != \"start\" {\n\t\towner = \"start\"\n\t}\n\tvar token [16]byte\n\tif _, err := rand.Read(token[:]); err != nil {","sourceCodeStart":41,"sourceCodeEnd":77,"githubUrl":"https://github.com/JuliusBrussee/caveman/blob/27d5a3981a347890211bb1bf2439e5c821a63bc9/proxy/internal/runstate/runstate.go#L41-L77","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nlisten := \"127.0.0.1:0\" // hope for random port\n\n// after\nl, _ := net.Listen(\"tcp\", \"127.0.0.1:0\")\nport := l.Addr().(*net.TCPAddr).Port\nlisten := fmt.Sprintf(\"127.0.0.1:%d\", port)","handlingStrategy":"validation","validationCode":"func normalizePort(listen string) (string, error) {\n    _, raw, err := net.SplitHostPort(listen)\n    if err != nil { return \"\", err }\n    p, err := strconv.Atoi(raw)\n    if err != nil || p < 1 || p > 65535 {\n        return \"\", fmt.Errorf(\"port %q must be 1-65535\", raw)\n    }\n    return fmt.Sprintf(\"127.0.0.1:%d\", p), nil\n}","typeGuard":"func isValidPort(s string) bool {\n    _, raw, err := net.SplitHostPort(s)\n    if err != nil { return false }\n    p, err := strconv.Atoi(raw)\n    return err == nil && p >= 1 && p <= 65535\n}","tryCatchPattern":"_, err := runstate.New(listen, mode, owner, version)\nif err != nil && strings.Contains(err.Error(), \"invalid listen port\") {\n    // reject config early with a clear message instead of half-starting the server\n}","preventionTips":["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'"],"tags":["config","validation","port","go"],"backgroundTag":null,"analyzedSha":"27d5a3981a347890211bb1bf2439e5c821a63bc9","analyzedAt":"2026-08-15T09:26:11.751Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}