JuliusBrussee/caveman · error
gemini bearer credential requires a valid x-goog-user-projec
Error message
gemini bearer credential requires a valid x-goog-user-project
What it means
For Gemini with a bearer-scheme credential (Gemini CLI OAuth/ADC), the adapter must send x-goog-user-project so Google attributes the call to the caller's quota/billing project. The code refuses to guess: if the inbound x-goog-user-project header is absent or fails validGoogleQuotaProject, the request is rejected. Remapping a bearer token to x-goog-api-key instead would break auth and mis-attribute quota, so this is a correctness fail-closed guard.
Source
Thrown at proxy/providers/adapter.go:476
if out.Get("anthropic-version") == "" {
out.Set("anthropic-version", "2023-06-01")
}
case "gemini":
if credential.Key != "" {
if credential.Scheme == "bearer" {
// Standalone env fallback uses this synthetic marker to reach the
// post-sanitization key resolver. It is not OAuth and must not be
// forwarded or subjected to OAuth quota-project validation.
if credential.Key == "no-key-required" {
break
}
// Gemini CLI OAuth/ADC credentials are bearer tokens. Remapping one to
// x-goog-api-key breaks authentication and silently defeats OAuth wrap.
// Google also requires the caller's quota project for user OAuth. Never
// guess it: wrong attribution is a billing and quota correctness bug.
quotaProject := strings.TrimSpace(req.Header.Get("x-goog-user-project"))
if !validGoogleQuotaProject(quotaProject) {
return nil, fmt.Errorf("gemini bearer credential requires a valid x-goog-user-project")
}
out.Set("authorization", "Bearer "+credential.Key)
out.Set("x-goog-user-project", quotaProject)
} else {
out.Set("x-goog-api-key", credential.Key)
}
}
case "vertex":
if credential.Key != "" {
out.Set("authorization", "Bearer "+credential.Key)
}
copyIfPresent(out, req.Header, "x-vertex-ai-llm-request-type")
copyIfPresent(out, req.Header, "x-goog-user-project")
case "azure_openai":
if credential.Key != "" {
// The standalone placeholder is a synthetic no-key marker used by
// callers that explicitly allow env-backed API-key fallback. It is
// not an Entra token and must not be forwarded as Authorization;View on GitHub (pinned to 27d5a3981a)
Solutions
- Have the client send a valid quota project header: x-goog-user-project: my-gcp-project on Gemini OAuth requests through the proxy.
- If you never intended OAuth, configure an API-key credential instead — the API-key branch sets x-goog-api-key and never hits this check.
- Ensure intermediaries forward the x-goog-user-project header (check droplists) and that its value is a real project id.
Example fix
# before curl http://proxy:8080/gemini/v1/... -H "authorization: Bearer $OAUTH_TOKEN" # -> error: bearer credential requires x-goog-user-project # after curl http://proxy:8080/gemini/v1/... \ -H "authorization: Bearer $OAUTH_TOKEN" \ -H "x-goog-user-project: my-gcp-project"
Defensive patterns
Strategy: validation
Validate before calling
// Before sending a Gemini OAuth request through the proxy:
qp := strings.TrimSpace(req.Header.Get("x-goog-user-project"))
if qp == "" || strings.ContainsAny(qp, " /") {
return errors.New("Gemini OAuth requests need a valid x-goog-user-project header")
}
// prefer a real project id: letters, digits, hyphens
if !projectIDRe.MatchString(qp) {
return fmt.Errorf("x-goog-user-project %q is not a valid project id", qp)
} Type guard
var projectIDRe = regexp.MustCompile(`^[a-z][a-z0-9-]{4,28}[a-z0-9]$`)
func hasValidQuotaProject(h http.Header) bool {
v := strings.TrimSpace(h.Get("x-goog-user-project"))
return projectIDRe.MatchString(v)
} Try / catch
if _, err := adapter.BuildUpstreamHeaders(credential, req); err != nil {
if strings.Contains(err.Error(), "x-goog-user-project") {
http.Error(w, "Gemini OAuth requires the x-goog-user-project header (your GCP quota project)", http.StatusBadRequest)
return
}
http.Error(w, err.Error(), http.StatusBadRequest)
} Prevention
- Configure the quota project wherever OAuth credentials are configured.
- Use API-key credentials when OAuth attribution is not needed.
- Ensure intermediaries forward x-goog-user-project.
When it happens
Trigger: Routing Gemini CLI (OAuth) traffic through the proxy without the client sending x-goog-user-project; the header present but empty, whitespace-padded, or malformed so validation fails; using an ADC/bearer credential where an API-key credential was intended.
Common situations: Switching a client from an API key to OAuth login and forgetting quota-project config; the header name typo'd by an intermediate proxy; GOOGLE_CLOUD_PROJECT set in env but the header never attached to proxied requests.
Related errors
- empty render text
- unsupported image token profile
- Claude usage refresh needs CAVEMAN_CLAUDE_USAGE_JSON or CAVE
- Claude usage request failed with HTTP %d
- provider %q has no configured upstream URL
AI-assisted analysis of JuliusBrussee/caveman@27d5a3981a (2026-08-15).
Data as JSON: /api/errors/746ec5fb97d7d48e.
Report an issue: GitHub.