{"record":{"id":"6af4c35be0527e6e","repo":"sipeed/picoclaw","slug":"unsupported-login-method-q-for-provider-q","errorCode":null,"errorMessage":"unsupported login method %q for provider %q","messagePattern":"unsupported login method %q for provider %q","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"web/backend/api/oauth.go","lineNumber":199,"sourceCode":"\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)\n\t\t\treturn\n\t\t}\n\n\t\tcred := &auth.AuthCredential{\n\t\t\tAccessToken: token,\n\t\t\tProvider:    provider,","sourceCodeStart":181,"sourceCodeEnd":217,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/web/backend/api/oauth.go#L181-L217","documentation":"Returned as HTTP 400 by POST /api/oauth/login when the method passes provider normalization but is not in that provider's method matrix (oauthProviderMethods). The matrix is strict: openai supports browser, device_code, token; anthropic supports only token; google-antigravity supports only browser. The method string is lowercased and trimmed, but must otherwise match exactly (underscore in \"device_code\", not hyphen).","triggerScenarios":"POST /api/oauth/login with {\"provider\":\"anthropic\",\"method\":\"browser\"} (anthropic has no browser flow), or {\"provider\":\"openai\",\"method\":\"device-code\"} (hyphen instead of underscore), or {\"provider\":\"google-antigravity\",\"method\":\"device_code\"} (antigravity is browser-only).","commonSituations":"Frontend that shows the same three login buttons for every provider; renaming method strings during an API migration; assuming device_code works everywhere because it works for OpenAI.","solutions":["Match the matrix exactly: openai → browser|device_code|token, anthropic → token, google-antigravity → browser.","Read the \"methods\" array from GET /api/oauth/providers per provider and only render/offer those login options.","Use the literal strings \"browser\", \"device_code\", \"token\" — note the underscore in device_code; the value is trimmed and lowercased but not otherwise normalized.","If you believe a method should be supported (e.g. anthropic browser OAuth), it requires a backend change (oauthProviderMethods plus an oauthConfigForProvider entry), not a config tweak."],"exampleFix":"// before\nconst method = provider === 'anthropic' ? 'device_code' : 'browser';\nawait fetch('/api/oauth/login', {method:'POST', body: JSON.stringify({provider, method})});\n// -> 400 unsupported login method \"device_code\" for provider \"anthropic\"\n\n// after\nconst supported = await getProviderMethods(provider); // from GET /api/oauth/providers\nconst method = supported.includes('device_code') ? 'device_code' : supported[0];","handlingStrategy":"validation","validationCode":"const MATRIX = {\n  'openai': ['browser', 'device_code', 'token'],\n  'anthropic': ['token'],\n  'google-antigravity': ['browser'],\n};\nfunction assertMethod(provider, method) {\n  const m = String(method ?? '').trim().toLowerCase();\n  if (!MATRIX[provider]?.includes(m)) throw new Error(`unsupported login method ${JSON.stringify(method)} for provider ${JSON.stringify(provider)}`);\n  return m;\n}","typeGuard":"function isMethodSupported(provider, method) {\n  return (MATRIX[provider] ?? []).includes(String(method ?? '').trim().toLowerCase());\n}","tryCatchPattern":null,"preventionTips":["Drive the login-method UI from the 'methods' array returned by GET /api/oauth/providers.","Use the exact literals 'browser', 'device_code', 'token' — underscore, not hyphen.","Remember anthropic is token-only and google-antigravity is browser-only."],"tags":["oauth","http-400","login-method","validation"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}