{"record":{"id":"5dde74543784aeb1","repo":"SubtitleEdit/subtitleedit","slug":"lens-returned-a-302-status-code-twice","errorCode":null,"errorMessage":"Lens returned a 302 status code twice","messagePattern":"Lens returned a 302 status code twice","errorType":"exception","errorClass":"CoreError","httpStatus":302,"severity":"error","filePath":"src/ui/Logic/Ocr/GoogleLens/Core.cs","lineNumber":162,"sourceCode":"            responseBytes = Helper.DecompressDeflate(responseBytes);\n        }\n        if (response.Content.Headers.ContentEncoding.Contains(\"br\"))\n        {\n            responseBytes = Helper.DecompressBrotli(responseBytes);\n        }\n        \n        var responseBody = Encoding.UTF8.GetString(responseBytes);\n\n        if (response.Headers.TryGetValues(\"set-cookie\", out var setCookieValues))\n        {\n            SetCookies(setCookieValues);\n        }\n\n        if (response.StatusCode == System.Net.HttpStatusCode.Found)\n        {\n            if (secondTry)\n            {\n                throw new CoreError(\"Lens returned a 302 status code twice\", (int)response.StatusCode, response.Headers, text);\n            }\n\n            var consentHeaders = HeaderData.GenerateHeaders();\n            consentHeaders[\"Content-Type\"] = \"application/x-www-form-urlencoded\";\n            consentHeaders[\"Referer\"] = \"https://consent.google.com/\";\n            consentHeaders[\"Origin\"] = \"https://consent.google.com\";\n\n            GenerateCookieHeader(consentHeaders);\n\n            var location = response.Headers.Location;\n            if (location == null)\n            {\n                throw new Exception(\"Location header not found\");\n            }\n\n            var redirectLink = new Uri(location.ToString());\n            var params2 = new Dictionary<string, string>\n            {","sourceCodeStart":144,"sourceCodeEnd":180,"githubUrl":"https://github.com/SubtitleEdit/subtitleedit/blob/17a9f0748781032255db3526b7215d2fb891e3af/src/ui/Logic/Ocr/GoogleLens/Core.cs#L144-L180","documentation":"Thrown by Core.FetchAsync when Google Lens responds with HTTP 302 (Found) on the retry pass (secondTry == true). The first 302 triggers a consent-screen handshake (posting to consent.google.com/save and retrying); a second consecutive 302 means the consent flow did not establish a usable session, so the client gives up rather than loop indefinitely. Wrapped in CoreError carrying status, headers, and response text.","triggerScenarios":"Repeated 302 redirects from Lens after the consent handshake — e.g. cookies were not accepted, the consent POST itself redirected again, or Google changed the consent flow. Network/proxy environments that strip Set-Cookie headers also cause this.","commonSituations":"A proxy or corporate firewall dropping Set-Cookie; an outdated hardcoded consent 'bl' token (boq_identityfrontenduiserver_20240129.02_p0) that Google rotated; regional consent requirements the handshake does not satisfy; rate limiting returning redirects.","solutions":["Ensure the HTTP client (HttpClientFactoryWithProxy.CreateHttpClientWithProxy) honors cookies and redirects are not auto-followed.","Update the consent parameters / endpoint tokens if Google changed them.","Retry the whole OCR call after a delay, or fall back to a different OCR engine.","Inspect the CoreError response text and headers to see where the redirect points."],"exampleFix":"// before\nvar result = await core.FetchAsync(formdata, dims);\n\n// after\ntry { var result = await core.FetchAsync(formdata, dims); }\ncatch (CoreError ex) when (ex.Message.Contains(\"302 twice\")) { return await FallbackOcrAsync(image); }","handlingStrategy":"retry","validationCode":"// Cannot fully prevent a server-side 302, but ensure cookies are enabled:\nvar client = HttpClientFactoryWithProxy.CreateHttpClientWithProxy(); // verify it uses a CookieContainer\n// Ensure HttpClient does not auto-follow redirects (so the consent flow can run).","typeGuard":null,"tryCatchPattern":"try { var r = await core.FetchAsync(formdata, dims); }\ncatch (CoreError ex) when (ex.Message.Contains(\"302 status code twice\"))\n{ await Task.Delay(2000); return await RetryOrFallbackAsync(); }","preventionTips":["Confirm the HTTP client honors Set-Cookie and does not auto-follow redirects.","Keep the consent 'bl' token current if Google rotates it.","Retry with backoff on transient redirect loops.","Provide a fallback OCR engine when Lens session setup fails."],"tags":["google-lens","ocr","network","http","cookies","consent","retry"],"backgroundTag":null,"analyzedSha":"17a9f0748781032255db3526b7215d2fb891e3af","analyzedAt":"2026-08-13T18:11:43.374Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}