{"record":{"id":"a4e62d9ffb3ccc82","repo":"router-for-me/CLIProxyAPI","slug":"method-not-allowed","errorCode":null,"errorMessage":"Method not allowed","messagePattern":"Method not allowed","errorType":"http","errorClass":null,"httpStatus":405,"severity":"warning","filePath":"internal/auth/claude/oauth_server.go","lineNumber":173,"sourceCode":"\t\treturn nil, err\n\tcase <-time.After(timeout):\n\t\treturn nil, fmt.Errorf(\"timeout waiting for OAuth callback\")\n\t}\n}\n\n// handleCallback handles the OAuth callback endpoint.\n// It extracts the authorization code and state from the callback URL,\n// validates the parameters, and sends the result to the waiting channel.\n//\n// Parameters:\n//   - w: The HTTP response writer\n//   - r: The HTTP request\nfunc (s *OAuthServer) handleCallback(w http.ResponseWriter, r *http.Request) {\n\tlog.Debug(\"Received OAuth callback\")\n\n\t// Validate request method\n\tif r.Method != http.MethodGet {\n\t\thttp.Error(w, \"Method not allowed\", http.StatusMethodNotAllowed)\n\t\treturn\n\t}\n\n\t// Extract parameters\n\tquery := r.URL.Query()\n\tcode := query.Get(\"code\")\n\tstate := query.Get(\"state\")\n\terrorParam := query.Get(\"error\")\n\n\t// Validate required parameters\n\tif errorParam != \"\" {\n\t\tlog.Errorf(\"OAuth error received: %s\", errorParam)\n\t\tresult := &OAuthResult{\n\t\t\tError: errorParam,\n\t\t}\n\t\ts.sendResult(result)\n\t\thttp.Error(w, fmt.Sprintf(\"OAuth error: %s\", errorParam), http.StatusBadRequest)\n\t\treturn","sourceCodeStart":155,"sourceCodeEnd":191,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/auth/claude/oauth_server.go#L155-L191","documentation":"The Claude OAuth callback HTTP handler only accepts GET. Any other method (POST from a misconfigured redirect, HEAD health probe, PUT/DELETE) receives a 405 with body \"Method not allowed\" and, importantly, no OAuthResult is sent — the waiting exchange is not completed by this path.","triggerScenarios":"Sending POST /callback?code=... (e.g. an IdP configured with response_mode=form_post), curl -X POST, or a load-balancer health check hitting the callback route with HEAD/POST.","commonSituations":"Identity providers or proxies that convert the redirect into a form POST; testing the callback with the wrong curl verb; monitoring probes firing during the auth window.","solutions":["Ensure the callback is reached via a plain browser GET redirect (standard authorization-code flow)","Test with curl without -X (defaults to GET) or curl -X GET \"http://127.0.0.1:PORT/callback?code=x&state=y\"","If your IdP forces form_post response mode, switch it to query (default) so the callback arrives as GET"],"exampleFix":"# before\ncurl -X POST \"http://127.0.0.1:1455/auth/callback?code=abc&state=xyz\"\n# 405 Method not allowed\n\n# after\ncurl \"http://127.0.0.1:1455/auth/callback?code=abc&state=xyz\"","handlingStrategy":"validation","validationCode":"if r.Method != http.MethodGet {\n    w.Header().Set(\"Allow\", http.MethodGet)\n    http.Error(w, \"GET only\", http.StatusMethodNotAllowed)\n    return\n}\n// (when proxying or probing the callback yourself, always issue GET)","typeGuard":"func isGet(r *http.Request) bool { return r.Method == http.MethodGet }","tryCatchPattern":"// Caller side: the channel wait surfaces failures — check the result\nresult, err := server.WaitForCode(ctx)\nif err != nil || result.Error != \"\" { log.Printf(\"oauth failed: %v %s\", err, result.Error) }","preventionTips":["Keep IdP response_mode as query (redirect), not form_post","Health-check a different endpoint than the OAuth callback","curl the callback without -X flags"],"tags":["claude","oauth","http-405","callback"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}