{"record":{"id":"9528348b8642b103","repo":"sipeed/picoclaw","slug":"exchanging-code-for-tokens-w","errorCode":null,"errorMessage":"exchanging code for tokens: %w","messagePattern":"exchanging code for tokens: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/auth/oauth.go","lineNumber":556,"sourceCode":"\t}\n\tif cfg.ClientSecret != \"\" {\n\t\tdata.Set(\"client_secret\", cfg.ClientSecret)\n\t}\n\n\ttokenURL := cfg.Issuer + \"/oauth/token\"\n\tif cfg.TokenURL != \"\" {\n\t\ttokenURL = cfg.TokenURL\n\t}\n\n\t// Determine provider name from config\n\tprovider := \"openai\"\n\tif cfg.TokenURL != \"\" && strings.Contains(cfg.TokenURL, \"googleapis.com\") {\n\t\tprovider = \"google-antigravity\"\n\t}\n\n\tresp, err := http.PostForm(tokenURL, data)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"exchanging code for tokens: %w\", err)\n\t}\n\tdefer resp.Body.Close()\n\n\tbody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"reading token exchange response: %w\", err)\n\t}\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"token exchange failed: %s\", string(body))\n\t}\n\n\treturn parseTokenResponse(body, provider)\n}\n\nfunc parseTokenResponse(body []byte, provider string) (*AuthCredential, error) {\n\tvar tokenResp struct {\n\t\tAccessToken  string `json:\"access_token\"`\n\t\tRefreshToken string `json:\"refresh_token\"`","sourceCodeStart":538,"sourceCodeEnd":574,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/auth/oauth.go#L538-L574","documentation":"ExchangeCodeForTokens (pkg/auth/oauth.go:556) failed before getting an HTTP response: http.PostForm to tokenURL (cfg.TokenURL or {Issuer}/oauth/token) with the authorization_code grant could not connect. The wrapped *url.Error carries the transport-level cause.","triggerScenarios":"Exchanging the code right after browser callback while offline; Issuer/TokenURL malformed or unreachable; proxy or TLS failure on the token endpoint; DNS outage between callback and exchange.","commonSituations":"User's network drops between authorizing in the browser and the CLI exchanging the code; TokenURL misconfigured; corporate SSL inspection rejecting the cert; the auth code typically expires in minutes, so fixing the network fast still matters.","solutions":["Read the wrapped *url.Error to identify dial/TLS/proxy failure","Verify tokenURL (print it — cfg.TokenURL overrides the issuer-derived default)","Restore connectivity, then retry the exchange promptly — authorization codes are short-lived; if expired, restart the login flow","Set proxy env vars if egress requires them"],"exampleFix":null,"handlingStrategy":"retry","validationCode":"tokenURL := cfg.TokenURL\nif tokenURL == \"\" {\n\ttokenURL = cfg.Issuer + \"/oauth/token\"\n}\nif u, err := url.Parse(tokenURL); err != nil || u.Scheme != \"https\" {\n\treturn fmt.Errorf(\"invalid token URL %q\", tokenURL)\n}","typeGuard":"func isTransportError(err error) bool {\n\tvar ue *url.Error\n\treturn errors.As(err, &ue)\n}","tryCatchPattern":"cred, err := auth.ExchangeCodeForTokens(cfg, code, verifier, redirectURI)\nif err != nil && isTransportError(err) && time.Since(codeIssuedAt) < 5*time.Minute {\n\t// code still fresh: one retry is reasonable\n\tcred, err = auth.ExchangeCodeForTokens(cfg, code, verifier, redirectURI)\n}","preventionTips":["Retry exchange quickly on transport errors — codes expire in minutes","Validate TokenURL and Issuer at startup","Ensure proxy/TLS config is correct before starting the login flow","If retry fails with invalid_grant, restart login instead of retrying again"],"tags":["network","oauth","authorization-code","dns","tls","go"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}