Wei-Shaw/sub2api · error
base URL must not include a fragment
Error message
base URL must not include a fragment
What it means
Thrown by normalizeKnownBaseURLPath when the supplied base URL has a URL fragment ('#...'). Fragments are purely client-side and meaningless in an API base URL, so their presence is treated as a misconfigured value rather than silently ignored.
Source
Thrown at backend/internal/pkg/xai/oauth.go:437
// normalizeKnownBaseURLPath 规范化 base URL 的 path 部分:
// - 官方主机固定使用 /v1 前缀(空 path 自动补齐,其余 path 拒绝);
// - 其他主机保留管理员配置的任意 path 前缀(第三方转发地址常见
// /xxx/v1 之类的路由前缀),空 path 仍按惯例补 /v1。
//
// 所有主机统一禁止 userinfo/query/fragment,并去除尾部斜杠。
func normalizeKnownBaseURLPath(raw string) (string, error) {
parsed, err := url.Parse(raw)
if err != nil || parsed.Scheme == "" || parsed.Host == "" {
return "", errors.New("invalid base URL")
}
if parsed.User != nil {
return "", errors.New("base URL must not include userinfo")
}
if parsed.ForceQuery || parsed.RawQuery != "" {
return "", errors.New("base URL must not include a query")
}
if parsed.Fragment != "" {
return "", errors.New("base URL must not include a fragment")
}
path := strings.TrimRight(parsed.Path, "/")
if path == "" {
parsed.Path = "/v1"
parsed.RawPath = ""
return strings.TrimRight(parsed.String(), "/"), nil
}
if path != "/v1" && IsOfficialBaseURLHost(parsed.Hostname()) {
return "", fmt.Errorf("base URL path must be /v1")
}
parsed.Path = path
parsed.RawPath = ""
return strings.TrimRight(parsed.String(), "/"), nil
}
// IsOfficialBaseURLHost 报告 host 是否属于官方 API / 区域 API / CLI 网关主机。
func IsOfficialBaseURLHost(host string) bool {
host = strings.ToLower(strings.TrimSpace(host))View on GitHub (pinned to 073e92d171)
Solutions
- Strip the fragment from the base URL ('https://api.x.ai/v1#auth' -> 'https://api.x.ai/v1').
- Copy the endpoint from the API's raw endpoint list rather than the address bar of a docs page with anchors.
- Add a pre-save lint in your config layer that rejects or trims '#'.
Example fix
// before baseURL := "https://api.x.ai/v1#authentication" // after baseURL := "https://api.x.ai/v1"
Defensive patterns
Strategy: validation
Validate before calling
func stripFragment(raw string) string {
if i := strings.Index(raw, "#"); i >= 0 {
return strings.TrimRight(raw[:i], "/")
}
return strings.TrimRight(raw, "/")
} Try / catch
if _, err := xai.NormalizeBaseURL(input); err != nil {
if strings.Contains(err.Error(), "fragment") {
input = input[:strings.Index(input, "#")] // retry once with fragment stripped
}
} Prevention
- Never copy API endpoints from docs pages with anchors
- Trim '#' on config ingest
- Test config parsing with fragment-bearing input
When it happens
Trigger: Passing a base URL like 'https://api.x.ai/v1#section' or 'https://api.x.ai/#' through normalizeKnownBaseURLPath. Parse succeeds but parsed.Fragment is non-empty.
Common situations: Base URL copy-pasted from documentation pages where the docs site appends an anchor (#authentication, #rate-limits); users manually trimming a URL but leaving a trailing '#'; template concatenation bugs that glue an anchor onto the endpoint.
Related errors
- base URL must not include a query
- dingtalk: internal_only requires app_type=internal
- token_exchange_failed
- userinfo_failed
- auth.dingtalk.callbackMissingToken
AI-assisted analysis of Wei-Shaw/sub2api@073e92d171 (2026-08-15).
Data as JSON: /api/errors/9a75ed2ebf803aee.
Report an issue: GitHub.