gastownhall/beads · error
--allowed-host %q contains whitespace; it must be a bare hos
Error message
--allowed-host %q contains whitespace; it must be a bare host name or IP
What it means
ValidateAllowedHost rejects entries containing whitespace. The allowed-host entry must be a bare host name or IP because it is compared against the request's Host header; whitespace would guarantee a mismatch and indicates a copy/paste or quoting mistake.
Source
Thrown at internal/httpapi/server.go:1862
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 nil
}
// allows reports whether a Host header value is one this server answers to.View on GitHub (pinned to 71377f2769)
Solutions
- Pass one bare host per flag occurrence; repeat --allowed-host for multiple entries
- Trim whitespace/newlines from config or env values before passing
- Use commas only if the CLI documents list parsing — otherwise supply each host separately
Example fix
// before --allowed-host "a.example.com b.example.com" // after --allowed-host a.example.com --allowed-host b.example.com
Defensive patterns
Strategy: validation
Validate before calling
if strings.ContainsAny(v, " \t\r\n") {
return fmt.Errorf("strip whitespace from %q before passing --allowed-host", v)
} Try / catch
if err := httpapi.ValidateAllowedHost(v); err != nil {
log.Fatalf("invalid --allowed-host %q: %v", v, err)
} Prevention
- Trim config/env values before passing
- Repeat --allowed-host per host instead of space-separated lists
- Quote shell args carefully to avoid trailing spaces
When it happens
Trigger: Calling ValidateAllowedHost with values like "bd.example.com\n", "example.com, example2.com" (comma+space lists), or a host with a trailing space from shell quoting.
Common situations: Operators paste multiple hosts on one flag separated by spaces, or a config file value picks up a trailing newline or indentation.
Related errors
- --allowed-host %q looks like a URL; pass just the host, with
- --allowed-host %q carries a port; the port is stripped from
- got %d close reasons for %d issue IDs; provide exactly one s
- cannot specify both --reason-file and --reason/--resolution/
- --reason-file %q is empty; close reason is required
AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30).
Data as JSON: /api/errors/7d26f577832a70fe.
Report an issue: GitHub.