JuliusBrussee/caveman · error
invalid listen address %q: %w
Error message
invalid listen address %q: %w
What it means
Returned by runstate.PortFromListen when net.SplitHostPort cannot parse the listen string into host and port. The run-state file is keyed by port, so the listen address must be a plain host:port TCP address before a State can be built.
Source
Thrown at proxy/internal/runstate/runstate.go:55
type PublicState struct {
Owner string `json:"owner"`
Mode string `json:"mode,omitempty"`
InstanceToken string `json:"instance_token,omitempty"`
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" {View on GitHub (pinned to 27d5a3981a)
Solutions
- Set listen to a bare host:port string such as "127.0.0.1:8080" or ":8080"
- Bracket IPv6 literals: "[::1]:8080"
- Strip scheme/prefixes if the value comes from a URL — keep only host:port
- Validate the flag at startup with net.SplitHostPort before passing it to runstate
Example fix
// before listen := "http://127.0.0.1:8080" st, err := runstate.New(listen, "record", "me", "1.0") // after listen := "127.0.0.1:8080" st, err := runstate.New(listen, "record", "me", "1.0")
Defensive patterns
Strategy: validation
Validate before calling
func validListen(s string) error {
if _, port, err := net.SplitHostPort(s); err != nil {
return fmt.Errorf("listen %q must be host:port", s)
} else if _, err := strconv.Atoi(port); err != nil {
return fmt.Errorf("listen port %q must be numeric", port)
}
return nil
} Type guard
func isHostPort(s string) bool {
_, _, err := net.SplitHostPort(s)
return err == nil
} Try / catch
port, err := runstate.PortFromListen(listen)
if err != nil {
if strings.Contains(err.Error(), "invalid listen address") {
return fmt.Errorf("config: fix listen to host:port (got %q)", listen)
}
} Prevention
- Source listen from a single validated config field, not free-form env text
- Bracket IPv6 literals as [::]:8080
- Add a startup config lint that runs SplitHostPort before any runstate call
When it happens
Trigger: Passing a listen value without a port ("localhost"), with an empty port ("localhost:"), with too many colons for a non-bracketed IPv6 literal ("::1:8080"), or with any other malformed host:port combination into runstate.New or PortFromListen.
Common situations: Configuring listen from a flag/env that accidentally holds a URL ("http://localhost:8080"), a Unix socket path, or an empty string; forgetting to bracket IPv6 addresses (should be "[::1]:8080"); trailing whitespace or quotes around the value in a YAML/env setting.
Related errors
- invalid listen port %q
- generic target %q requires a non-empty source path
- ssrf: scheme %q not permitted (managed mode requires https)
- cave_budget_denomination_ambiguous
- cave_budget_max_invalid
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/323e65022fdc9379.
Report an issue: GitHub.