JuliusBrussee/caveman · error
listen address %q must be loopback host:port
Error message
listen address %q must be loopback host:port
What it means
config.validateListen rejects the proxy's listen address before startup when it cannot be split into a host and a non-empty port. The caveman standalone proxy is unauthenticated BYOK, so the address must be a concrete loopback host:port. Malformed input such as '127.0.0.1' (no port), ':8080' (no host usable later), or extra colons fails here.
Source
Thrown at proxy/internal/config/config.go:126
}
}
cfg = cfg.withDefaults()
if err := validateListen(cfg.Listen); err != nil {
return Config{}, err
}
if err := cfg.validateCompat(); err != nil {
return Config{}, err
}
return cfg, nil
}
// validateListen keeps standalone's unauthenticated BYOK proxy local to one
// operator. Binding an empty, wildcard, or non-loopback host would expose every
// configured provider credential to the network with no inbound authentication.
func validateListen(listen string) error {
host, port, err := net.SplitHostPort(strings.TrimSpace(listen))
if err != nil || port == "" {
return fmt.Errorf("listen address %q must be loopback host:port", listen)
}
if strings.EqualFold(host, "localhost") {
return nil
}
ip := net.ParseIP(host)
if ip == nil || !ip.IsLoopback() {
return fmt.Errorf("listen address %q is not loopback; standalone proxy has no inbound authentication", listen)
}
return nil
}
func (c Config) withDefaults() Config {
if label := env.String("CAVEMAN_LABEL", ""); label != "" {
c.Label = label
}
if c.Label == "" {
c.Label = "local"
}View on GitHub (pinned to 27d5a3981a)
Solutions
- Set listen to an explicit loopback host:port, e.g. '127.0.0.1:8080', 'localhost:8080', or '[::1]:8080'
- If you need a different port only, keep the host loopback and change just the port
- Wrap IPv6 loopback in brackets so SplitHostPort parses the port correctly
- To expose the proxy beyond loopback, put an authenticated reverse proxy in front instead of widening the bind address
Example fix
// before listen: "127.0.0.1" // Error[1060]: must be loopback host:port // after listen: "127.0.0.1:8080"
Defensive patterns
Strategy: validation
Validate before calling
// validateListenLike mirrors config.validateListen's parse step.
func checkListenAddr(listen string) error {
host, port, err := net.SplitHostPort(strings.TrimSpace(listen))
if err != nil || port == "" {
return fmt.Errorf("listen %q: missing host:port", listen)
}
return nil
}
if err := checkListenAddr(cfg.Listen); err != nil { /* fix config before start */ } Prevention
- Hardcode the full loopback host:port ('127.0.0.1:8080') in templates and docs
- Bracket IPv6 hosts: '[::1]:8080'
- Lint caveman.yaml in CI with the same SplitHostPort check used in production
When it happens
Trigger: Setting listen (caveman.yaml or CAVEMAN_LISTEN env) to a host without a port ('localhost'), a port without a host (':9111' parses but usually fails the loopback check), '127.0.0.1:8080:extra', or a port string that SplitHostPort rejects entirely.
Common situations: Copying a bare hostname from docs into the listen field; Docker-minded habits of binding ':PORT'; trailing whitespace variants that SplitHostPort still fails on; IPv6 addresses written without brackets ('::1:8080' instead of '[::1]:8080').
Related errors
- listen address %q is not loopback; standalone proxy has no i
- cave_mastra_terminal_failure
- cave_harness_incomplete_evidence
- signature check failed for ${artifact} — partial download de
- invalid listen address %q: %w
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/2255303a268092dd.
Report an issue: GitHub.