{"record":{"id":"907e155bd7cfceee","repo":"sipeed/picoclaw","slug":"unsupported-provider-q","errorCode":null,"errorMessage":"unsupported provider %q","messagePattern":"unsupported provider %q","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"web/backend/api/oauth.go","lineNumber":193,"sourceCode":"\tif err != nil {\n\t\thttp.Error(w, \"failed to read request body\", http.StatusBadRequest)\n\t\treturn\n\t}\n\tdefer r.Body.Close()\n\n\tvar req struct {\n\t\tProvider string `json:\"provider\"`\n\t\tMethod   string `json:\"method\"`\n\t\tToken    string `json:\"token\"`\n\t}\n\tif err = json.Unmarshal(body, &req); err != nil {\n\t\thttp.Error(w, fmt.Sprintf(\"invalid JSON: %v\", err), http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tprovider, err := normalizeOAuthProvider(req.Provider)\n\tif err != nil {\n\t\thttp.Error(w, err.Error(), http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tmethod := strings.ToLower(strings.TrimSpace(req.Method))\n\tif !isOAuthMethodSupported(provider, method) {\n\t\thttp.Error(\n\t\t\tw,\n\t\t\tfmt.Sprintf(\"unsupported login method %q for provider %q\", method, provider),\n\t\t\thttp.StatusBadRequest,\n\t\t)\n\t\treturn\n\t}\n\n\tswitch method {\n\tcase oauthMethodToken:\n\t\ttoken := strings.TrimSpace(req.Token)\n\t\tif token == \"\" {\n\t\t\thttp.Error(w, \"token is required\", http.StatusBadRequest)","sourceCodeStart":175,"sourceCodeEnd":211,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/web/backend/api/oauth.go#L175-L211","documentation":"Returned as HTTP 400 by POST /api/oauth/login when normalizeOAuthProvider rejects the \"provider\" field. The backend only knows three providers: \"openai\", \"anthropic\", and \"google-antigravity\" (the alias \"antigravity\" is also accepted and canonicalized). Any other value is echoed back quoted, e.g. unsupported provider \"open AI\". The value is lowercased and trimmed before matching, so only spelling variants of those three names pass.","triggerScenarios":"POST /api/oauth/login with body {\"provider\":\"azure\",\"method\":\"token\",\"token\":\"sk-...\"} or any typo like \"OpenAI \" (spaces are fine, but \"open-ai\" is not). Also triggered by sending a provider name that exists in the gateway config (e.g. \"gemini\") but has no OAuth entry in the oauthProviderMethods map.","commonSituations":"Copy-pasting provider names from the model config (which uses names like \"antigravity\") instead of the OAuth provider list; running an older build that predates google-antigravity support; frontend dropdowns that list every configured model provider rather than only the three OAuth-capable ones.","solutions":["Use exactly one of: \"openai\", \"anthropic\", \"google-antigravity\" (alias \"antigravity\" also works) in the provider field.","Fetch GET /api/oauth/providers and drive your UI from the returned provider list instead of hardcoding names.","If you need a provider not in that list, upgrade picoclaw — new providers are added to oauthProviderOrder/normalizeOAuthProvider, not via config.","Check for invisible whitespace or unicode look-alike characters if a name that looks correct still fails (trim/lowercase is applied, but homoglyphs are not)."],"exampleFix":"// before (curl)\ncurl -X POST http://localhost:8080/api/oauth/login \\\n  -d '{\"provider\":\"open-ai\",\"method\":\"token\",\"token\":\"sk-...\"}'\n// -> 400 unsupported provider \"open-ai\"\n\n// after\ncurl -X POST http://localhost:8080/api/oauth/login \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"provider\":\"openai\",\"method\":\"token\",\"token\":\"sk-...\"}'","handlingStrategy":"validation","validationCode":"const SUPPORTED = new Set(['openai', 'anthropic', 'google-antigravity', 'antigravity']);\nfunction assertProvider(provider) {\n  const p = String(provider ?? '').trim().toLowerCase();\n  if (!SUPPORTED.has(p)) throw new Error(`unsupported provider ${JSON.stringify(provider)}`);\n  return p === 'antigravity' ? 'google-antigravity' : p;\n}","typeGuard":"function isOAuthProvider(v) {\n  return ['openai', 'anthropic', 'google-antigravity'].includes(String(v ?? '').trim().toLowerCase());\n}","tryCatchPattern":null,"preventionTips":["Source the provider list from GET /api/oauth/providers instead of hardcoding or reusing model-config provider names.","Normalize provider strings (trim + lowercase) before sending.","Remember the alias: 'antigravity' is accepted and mapped to 'google-antigravity'."],"tags":["oauth","http-400","provider","validation"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}