{"record":{"id":"040551db952d7b2e","repo":"router-for-me/CLIProxyAPI","slug":"failed-to-create-refresh-request-w-040551","errorCode":null,"errorMessage":"failed to create refresh request: %w","messagePattern":"failed to create refresh request: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/auth/codex/openai_auth.go","lineNumber":223,"sourceCode":"\t}\n\ttokenData, ok := result.(*CodexTokenData)\n\tif !ok || tokenData == nil {\n\t\treturn nil, fmt.Errorf(\"token refresh failed: invalid single-flight result\")\n\t}\n\treturn tokenData, nil\n}\n\nfunc (o *CodexAuth) refreshTokensSingleFlight(ctx context.Context, refreshToken string) (*CodexTokenData, error) {\n\tdata := url.Values{\n\t\t\"client_id\":     {ClientID},\n\t\t\"grant_type\":    {\"refresh_token\"},\n\t\t\"refresh_token\": {refreshToken},\n\t\t\"scope\":         {\"openid profile email\"},\n\t}\n\n\treq, errReq := http.NewRequestWithContext(ctx, \"POST\", TokenURL, strings.NewReader(data.Encode()))\n\tif errReq != nil {\n\t\treturn nil, fmt.Errorf(\"failed to create refresh request: %w\", errReq)\n\t}\n\n\treq.Header.Set(\"Content-Type\", \"application/x-www-form-urlencoded\")\n\treq.Header.Set(\"Accept\", \"application/json\")\n\n\tresp, errDo := o.httpClient.Do(req)\n\tif errDo != nil {\n\t\treturn nil, fmt.Errorf(\"token refresh request failed: %w\", errDo)\n\t}\n\tdefer func() {\n\t\tif errClose := resp.Body.Close(); errClose != nil {\n\t\t\tlog.Errorf(\"token refresh response body close error: %v\", errClose)\n\t\t}\n\t}()\n\n\tbody, errRead := io.ReadAll(resp.Body)\n\tif errRead != nil {\n\t\treturn nil, fmt.Errorf(\"failed to read refresh response: %w\", errRead)","sourceCodeStart":205,"sourceCodeEnd":241,"githubUrl":"https://github.com/router-for-me/CLIProxyAPI/blob/78f0c4079e3e6273d65d03b5549cffc898703264/internal/auth/codex/openai_auth.go#L205-L241","documentation":"http.NewRequestWithContext failed while building the refresh-token POST in refreshTokensSingleFlight. Like the exchange path, method and TokenURL are hardcoded, so a failure here practically means the passed context is nil or already canceled — not a network problem. Distinguished from 207 by occurring before any socket is opened.","triggerScenarios":"Refresh attempted with a canceled context (shutdown in progress); nil context reaching this function through a custom integration; modified TokenURL constant.","commonSituations":"Config hot-reload or process shutdown canceling the parent context exactly as a background refresh fires; SDK embedders passing the wrong context into the refresh path.","solutions":["Check for prior context cancellation with errors.Is(err, context.Canceled) on the wrapped error.","Give the refresh path its own short-lived context derived from Background, not one tied to request/shutdown lifetimes.","If embedding via sdk/cliproxy, verify the context passed into token refresh calls is non-nil."],"exampleFix":"// before\nctx := requestCtx // canceled when the request is done\n_, _ = auth.RefreshTokensWithRetry(ctx, token, 3)\n\n// after\nrefreshCtx, cancel := context.WithTimeout(context.Background(), 60*time.Second)\ndefer cancel()\n_, _ = auth.RefreshTokensWithRetry(refreshCtx, token, 3)","handlingStrategy":"validation","validationCode":"if ctx == nil || ctx.Err() != nil {\n    ctx = context.Background() // or return an explicit error\n}\n_, err := auth.RefreshTokens(ctx, refreshToken)","typeGuard":null,"tryCatchPattern":"td, err := auth.RefreshTokens(ctx, rt)\nif err != nil {\n    if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {\n        // request-building failure from a dead context; retry with a fresh one\n    }\n}","preventionTips":["Derive refresh contexts from context.Background with their own timeout.","Do not tie background refreshes to request or shutdown lifetimes."],"tags":["oauth","codex","context","token-refresh"],"backgroundTag":null,"analyzedSha":"78f0c4079e3e6273d65d03b5549cffc898703264","analyzedAt":"2026-08-15T12:26:37.444Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}