{"record":{"id":"4e28103f9347da58","repo":"sipeed/picoclaw","slug":"unsupported-login-method","errorCode":null,"errorMessage":"unsupported login method","messagePattern":"unsupported login method","errorType":"http","errorClass":null,"httpStatus":400,"severity":"warning","filePath":"web/backend/api/oauth.go","lineNumber":317,"sourceCode":"\t\t\tExpiresAt:    now.Add(oauthBrowserFlowTTL),\n\t\t\tCodeVerifier: pkce.CodeVerifier,\n\t\t\tOAuthState:   state,\n\t\t\tRedirectURI:  redirectURI,\n\t\t}\n\t\th.storeOAuthFlow(flow)\n\n\t\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t\t_ = json.NewEncoder(w).Encode(map[string]any{\n\t\t\t\"status\":     \"ok\",\n\t\t\t\"provider\":   provider,\n\t\t\t\"method\":     method,\n\t\t\t\"flow_id\":    flow.ID,\n\t\t\t\"auth_url\":   authURL,\n\t\t\t\"expires_at\": flow.ExpiresAt.Format(time.RFC3339),\n\t\t})\n\t\treturn\n\tdefault:\n\t\thttp.Error(w, \"unsupported login method\", http.StatusBadRequest)\n\t}\n}\n\nfunc (h *Handler) handleGetOAuthFlow(w http.ResponseWriter, r *http.Request) {\n\tflowID := strings.TrimSpace(r.PathValue(\"id\"))\n\tif flowID == \"\" {\n\t\thttp.Error(w, \"missing flow id\", http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tflow, ok := h.getOAuthFlow(flowID)\n\tif !ok {\n\t\thttp.Error(w, \"flow not found\", http.StatusNotFound)\n\t\treturn\n\t}\n\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t_ = json.NewEncoder(w).Encode(flowToResponse(flow))","sourceCodeStart":299,"sourceCodeEnd":335,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/web/backend/api/oauth.go#L299-L335","documentation":"Returned as HTTP 400 by the default branch of the method switch in POST /api/oauth/login. It is a defensive fallback: every method accepted by isOAuthMethodSupported (browser, device_code, token) has an explicit case, so with a consistent build this branch is unreachable. Hitting it means the binary's method matrix and switch have diverged — someone added a method string to oauthProviderMethods without implementing its case (or vice versa in tests).","triggerScenarios":"POST /api/oauth/login {\"provider\":\"openai\",\"method\":\"<new-method>\"} where \"<new-method>\" was added to oauthProviderMethods in the deployed binary but the switch has no case for it. Not producible by any request against an unmodified build.","commonSituations":"Running a custom fork or unreleased branch that extends the login methods; stale cached build after pulling new code; a test double that injects an unsupported method.","solutions":["Rebuild/redeploy the backend from a consistent source tree so oauthProviderMethods and the switch agree.","If you are extending the backend, add a switch case for every new method constant you add to the matrix.","Add a unit test iterating oauthProviderMethods and asserting handleOAuthLogin returns non-400-default for each advertised method."],"exampleFix":"// before: method advertised but not implemented\nconst (\n    oauthMethodBrowser = \"browser\"\n    oauthMethodDeviceCode = \"device_code\"\n    oauthMethodToken = \"token\"\n    oauthMethodSso = \"sso\" // added to matrix only -> 400 unsupported login method\n)\n\n// after: implement it or do not advertise it\nswitch method {\ncase oauthMethodToken: ...\ncase oauthMethodDeviceCode: ...\ncase oauthMethodBrowser: ...\ncase oauthMethodSso:\n    // handle sso\n}","handlingStrategy":"validation","validationCode":"const KNOWN_METHODS = new Set(['browser', 'device_code', 'token']);\nif (!KNOWN_METHODS.has(method)) throw new Error(`unknown login method ${method}`);","typeGuard":"function isKnownMethod(m) { return ['browser', 'device_code', 'token'].includes(m); }","tryCatchPattern":null,"preventionTips":["Pin clients to the documented method literals.","Backend developers: add a switch case for every constant added to oauthProviderMethods, plus a matrix/switch consistency test.","If this surfaces in a stock build, the binary is inconsistent — rebuild from a clean tree."],"tags":["oauth","http-400","invariant","dead-branch","code-drift"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}