gastownhall/beads · error
--allowed-host is empty; pass the Host header value clients
Error message
--allowed-host is empty; pass the Host header value clients send, such as bd-myproject.beads.svc.cluster.local
What it means
ValidateAllowedHost rejects an --allowed-host value that is empty after trimming. Allowed hosts are matched against the incoming request's Host header (with the port stripped by hostOnly), so an empty entry can never match and the server would reject every client. The error names the exact thing to pass: the bare Host header value clients send.
Source
Thrown at internal/httpapi/server.go:1859
if !containsIP(p.ips, ip) {
p.ips = append(p.ips, ip)
}
continue
}
p.names[h] = true
}
return p
}
// ValidateAllowedHost refuses an allowlist entry that is not a bare host.
//
// The Host header's port is stripped before matching (hostOnly), so an entry
// carrying one would silently never match — and an operator who wrote it would
// reasonably read the startup line as proof that it does. A URL, a path or
// embedded whitespace is the same mistake in a louder form.
func ValidateAllowedHost(v string) error {
if strings.TrimSpace(v) == "" {
return errors.New("--allowed-host is empty; pass the Host header value clients send, such as bd-myproject.beads.svc.cluster.local")
}
if strings.ContainsAny(v, " \t\r\n") {
return fmt.Errorf("--allowed-host %q contains whitespace; it must be a bare host name or IP", v)
}
if strings.ContainsAny(v, "/@") {
return fmt.Errorf("--allowed-host %q looks like a URL; pass just the host, with no scheme and no path", v)
}
// An IPv6 address is spelled in brackets in a Host header, so an operator
// copying one off the wire types it that way. hostOnly strips them before
// matching, so the entry works; refusing it here — with a message about a
// port it does not have — would be the validation lying about the policy.
if net.ParseIP(strings.TrimSuffix(strings.TrimPrefix(v, "["), "]")) != nil {
return nil
}
if strings.Contains(v, ":") {
return fmt.Errorf("--allowed-host %q carries a port; the port is stripped from a request's Host before matching, so an entry with one could never match", v)
}
return nilView on GitHub (pinned to 71377f2769)
Solutions
- Pass the actual Host header value clients use, e.g. --allowed-host bd-myproject.beads.svc.cluster.local (no scheme, no path, no port)
- Fix the surrounding config/secret/templating so the value is not empty at startup
- If multiple hosts are allowed, pass each as its own non-empty --allowed-host entry
Example fix
// before bd serve --allowed-host="" // after bd serve --allowed-host=bd-myproject.beads.svc.cluster.local
Defensive patterns
Strategy: validation
Validate before calling
v := strings.TrimSpace(flagAllowedHost)
if v == "" {
return errors.New("--allowed-host must be the Host header value clients send, e.g. bd-myproject.beads.svc.cluster.local")
}
if err := httpapi.ValidateAllowedHost(v); err != nil {
return err
} Prevention
- Fail fast at deploy time: validate config templates render a non-empty allowed-host before starting the server
- Never pass scheme, path, or port — store only bare host names in config
- Add a CI check running httpapi.ValidateAllowedHost over all environment configs
When it happens
Trigger: Starting the server with --allowed-host="" or only whitespace; programmatically calling ValidateAllowedHost("") or with a value of only spaces/tabs before saving an allowed-host entry.
Common situations: An empty environment variable or config default silently substituted for the flag; templated deploy configs where the host placeholder resolved to empty; forgetting the flag entirely and defaulting to empty string.
Related errors
- httpapi: a configured role fires this workspace's hooks; thi
- httpapi: the configured provider fires this workspace's hook
- httpapi: this server has no unit-of-work provider; it answer
- remote URL cannot be empty
- multiple .doltcfg directories detected
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/13f850e6512fad21.
Report an issue: GitHub.