{"record":{"id":"06d301cc7b7820da","repo":"sipeed/picoclaw","slug":"refreshing-token-w","errorCode":null,"errorMessage":"refreshing token: %w","messagePattern":"refreshing token: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/auth/oauth.go","lineNumber":460,"sourceCode":"\n\tdata := url.Values{\n\t\t\"client_id\":     {cfg.ClientID},\n\t\t\"grant_type\":    {\"refresh_token\"},\n\t\t\"refresh_token\": {cred.RefreshToken},\n\t\t\"scope\":         {\"openid profile email\"},\n\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\tresp, err := http.PostForm(tokenURL, data)\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"refreshing token: %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 refresh response: %w\", err)\n\t}\n\tif resp.StatusCode != http.StatusOK {\n\t\treturn nil, fmt.Errorf(\"token refresh failed: %s\", string(body))\n\t}\n\n\trefreshed, err := parseTokenResponse(body, cred.Provider)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif refreshed.RefreshToken == \"\" {\n\t\trefreshed.RefreshToken = cred.RefreshToken\n\t}","sourceCodeStart":442,"sourceCodeEnd":478,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/pkg/auth/oauth.go#L442-L478","documentation":"RefreshAccessToken (pkg/auth/oauth.go:460) failed at the transport level: http.PostForm to tokenURL (cfg.TokenURL if set, else {Issuer}/oauth/token) never produced a response. The wrapped error is a *url.Error naming the real cause such as DNS, refused connection, TLS, or proxy failure.","triggerScenarios":"Calling RefreshAccessToken while offline, with a bad Issuer/TokenURL (typo, wrong scheme), through a required-but-unconfigured proxy, or against a TLS endpoint whose cert the client rejects.","commonSituations":"Token refresh attempted in CI/offline daemons with no egress; TokenURL left over from another environment; HTTPS_PROXY unset behind a corporate firewall; issuer certificate rotation breaking x509 verification.","solutions":["Read the wrapped *url.Error cause — it distinguishes dial/x509/proxyconnect precisely","Print and validate tokenURL: cfg.TokenURL overrides cfg.Issuer+\"/oauth/token\", so a stale TokenURL is a classic culprit","curl -v the token URL to confirm reachability from the same environment","Configure proxy env vars or fix the URL; refresh is usually safe to retry with backoff once fixed","Fall back to re-login if the credential is near expiry and refresh cannot succeed"],"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 == \"\" || u.Host == \"\" {\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":"refreshed, err := auth.RefreshAccessToken(cred, cfg)\nif err != nil && isTransportError(err) {\n\t// transient network failure: safe to retry with backoff\n\ttime.Sleep(2 * time.Second)\n\trefreshed, err = auth.RefreshAccessToken(cred, cfg)\n}","preventionTips":["Start refresh well before AccessToken expiry so retries fit in the window","Validate TokenURL at config load; remember it overrides the issuer default","Retry only transport errors — status failures need diagnosis, not retries","Monitor connectivity to the token endpoint in long-running processes"],"tags":["network","oauth","refresh-token","dns","tls","go"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}