{"record":{"id":"e9c91fcba6342013","repo":"charmbracelet/crush","slug":"failed-to-refresh-oauth-token-status-code-d","errorCode":null,"errorMessage":"failed to refresh OAuth token: status code %d","messagePattern":"failed to refresh OAuth token: status code (.+?)","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/client/config.go","lineNumber":160,"sourceCode":"\tif err := json.NewDecoder(rsp.Body).Decode(&result); err != nil {\n\t\treturn nil, false, fmt.Errorf(\"failed to decode import copilot response: %w\", err)\n\t}\n\treturn result.Token, result.Success, nil\n}\n\n// RefreshOAuthToken refreshes an OAuth token for a provider on the\n// server.\nfunc (c *Client) RefreshOAuthToken(ctx context.Context, id string, scope config.Scope, providerID string) error {\n\trsp, err := c.post(ctx, fmt.Sprintf(\"/workspaces/%s/config/refresh-oauth\", id), nil, jsonBody(struct {\n\t\tScope      config.Scope `json:\"scope\"`\n\t\tProviderID string       `json:\"provider_id\"`\n\t}{Scope: scope, ProviderID: providerID}), http.Header{\"Content-Type\": []string{\"application/json\"}})\n\tif err != nil {\n\t\treturn fmt.Errorf(\"failed to refresh OAuth token: %w\", err)\n\t}\n\tdefer rsp.Body.Close()\n\tif rsp.StatusCode != http.StatusOK {\n\t\treturn fmt.Errorf(\"failed to refresh OAuth token: status code %d\", rsp.StatusCode)\n\t}\n\treturn nil\n}\n\n// ProjectNeedsInitialization checks if the project needs\n// initialization.\nfunc (c *Client) ProjectNeedsInitialization(ctx context.Context, id string) (bool, error) {\n\trsp, err := c.get(ctx, fmt.Sprintf(\"/workspaces/%s/project/needs-init\", id), nil, nil)\n\tif err != nil {\n\t\treturn false, fmt.Errorf(\"failed to check project init: %w\", err)\n\t}\n\tdefer rsp.Body.Close()\n\tif rsp.StatusCode != http.StatusOK {\n\t\treturn false, fmt.Errorf(\"failed to check project init: status code %d\", rsp.StatusCode)\n\t}\n\tvar result struct {\n\t\tNeedsInit bool `json:\"needs_init\"`\n\t}","sourceCodeStart":142,"sourceCodeEnd":178,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/client/config.go#L142-L178","documentation":"RefreshOAuthToken expects HTTP 200 from /workspaces/{id}/config/refresh-oauth. Any other status (4xx/5xx) produces this error. It means the server received the request but refused or failed it — unlike error 270, the network round-trip succeeded.","triggerScenarios":"Calling RefreshOAuthToken and the server responds with a non-200 status, e.g. 401 when the refresh grant itself is invalid, 404 for an unknown workspace id, or 500 on a server-side provider token exchange failure.","commonSituations":"The stored refresh token was revoked or expired beyond the grace window; the providerID no longer exists in the workspace config; the caller lacks scope permission for the workspace; server bug during provider token rotation.","solutions":["Log rsp.StatusCode variants: re-authenticate the provider interactively if 401/403 (refresh grant is dead)","Verify the workspace id and providerID are correct if you see 404","Check server logs for 5xx responses; this is a server-side token-exchange failure","Retry with backoff only for 429/5xx; do not retry 4xx client errors"],"exampleFix":"// before\nif err := client.RefreshOAuthToken(ctx, id, scope, providerID); err != nil {\n    return err\n}\n// after\nif err := client.RefreshOAuthToken(ctx, id, scope, providerID); err != nil {\n    if strings.Contains(err.Error(), \"status code 401\") {\n        return requireReauthentication(providerID)\n    }\n    return err\n}","handlingStrategy":"try-catch","validationCode":"// Ensure the provider is registered in the workspace before refreshing\nproviders, err := client.ListProviders(ctx, id)\nif err != nil {\n    return err\n}\nfound := false\nfor _, p := range providers {\n    if p.ID == providerID {\n        found = true\n    }\n}\nif !found {\n    return fmt.Errorf(\"provider %q not in workspace %s\", providerID, id)\n}","typeGuard":"func isStatusError(err error, code int) bool {\n    return err != nil && strings.Contains(err.Error(), fmt.Sprintf(\"status code %d\", code))\n}","tryCatchPattern":"if err := c.RefreshOAuthToken(ctx, id, scope, pid); err != nil {\n    if isStatusError(err, 401) || isStatusError(err, 403) {\n        return triggerInteractiveLogin(pid)\n    }\n    return err\n}","preventionTips":["Distinguish 4xx (do not retry) from 429/5xx (retry with backoff)","Re-authenticate providers proactively before refresh grants expire","Validate workspace and provider ids before calling refresh","Log response statuses server-side to diagnose refresh failures"],"tags":["http-client","oauth","http-status"],"backgroundTag":"oauth-token-refresh-failed","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}