uber-go/zap · error
must start with a letter
Error message
must start with a letter
What it means
Per RFC 3986 §3.1, a URI scheme must begin with an ASCII letter. normalizeScheme lowercases the input and returns errors.New("must start with a letter") when the first byte is outside a-z, before validating the remaining characters.
Source
Thrown at sink.go:165
return sr.newFileSinkFromPath(u.Path)
}
func (sr *sinkRegistry) newFileSinkFromPath(path string) (Sink, error) {
switch path {
case "stdout":
return nopCloserSink{os.Stdout}, nil
case "stderr":
return nopCloserSink{os.Stderr}, nil
}
return sr.openFile(path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0o666)
}
func normalizeScheme(s string) (string, error) {
// https://tools.ietf.org/html/rfc3986#section-3.1
s = strings.ToLower(s)
if first := s[0]; 'a' > first || 'z' < first {
return "", errors.New("must start with a letter")
}
for i := 1; i < len(s); i++ { // iterate over bytes, not runes
c := s[i]
switch {
case 'a' <= c && c <= 'z':
continue
case '0' <= c && c <= '9':
continue
case c == '.' || c == '+' || c == '-':
continue
}
return "", fmt.Errorf("may not contain %q", c)
}
return s, nil
}
View on GitHub (pinned to bbd4ecbd87)
Solutions
- Rename the scheme to start with a letter, e.g. "custom" or "s3custom"
- Sanitize/validate the scheme with a regex like ^[a-z][a-z0-9+.-]*$ before use
Example fix
// before
zap.RegisterSink("1custom", factory)
// after
zap.RegisterSink("custom1", factory) Defensive patterns
Strategy: validation
Validate before calling
var schemeRe = regexp.MustCompile(`^[a-z][a-z0-9+.-]*$`)
if !schemeRe.MatchString(strings.ToLower(scheme)) {
return fmt.Errorf("invalid scheme %q", scheme)
}
err := zap.RegisterSink(scheme, factory) Prevention
- Ensure schemes start with an ASCII letter
- Validate against RFC 3986 scheme regex before registering or building sink URLs
- Watch for templating errors injecting digits/punctuation into scheme positions
When it happens
Trigger: Registering or opening a sink whose scheme starts with a non-letter character, e.g. RegisterSink("1custom", f) or opening a URL like "123://path".
Common situations: Malformed connection strings/DSNs; typo'd or machine-generated scheme names; schemes containing leading digits or symbols from templating errors.
Related errors
- can't register a sink factory for empty string
- %q is not a valid scheme: %v
- can't parse %q as a URL: %v
- user and password not allowed with file URLs: got %v
- fragments not allowed with file URLs: got %v
AI-assisted analysis of uber-go/zap@bbd4ecbd87 (2026-08-31).
Data as JSON: /api/errors/bfde5bed3ceb3891.
Report an issue: GitHub.