{"record":{"id":"4a304415440134bf","repo":"HeyPuter/puter","slug":"request-timeout","errorCode":"request_timeout","errorMessage":"Request timeout.","messagePattern":"Request timeout\\.","errorType":"http","errorClass":"HttpError","httpStatus":408,"severity":"warning","filePath":"src/backend/controllers/auth/AuthController.ts","lineNumber":272,"sourceCode":"        const expectedAppUid =\n            await this.services.auth.appUidFromOrigin(reqOrigin);\n\n        const { resolve, promise } = Promise.withResolvers<void>();\n\n        let token: string | null = null;\n        const listener = (_key: string, value: { authtoken: string }) => {\n            token = value.authtoken;\n            resolve();\n        };\n        this.clients.event.on(`pubsub.login.${session}`, listener);\n\n        const timeout = new Promise<void>((resolve) =>\n            setTimeout(resolve, 10000),\n        );\n        await Promise.race([promise, timeout]);\n        this.clients.event.off(`pubsub.login.${session}`, listener);\n        if (!token) {\n            throw new HttpError(408, 'Request timeout.', {\n                legacyCode: 'request_timeout',\n            });\n        }\n\n        // Audience check. The postMessage hand-off this relay stands in for\n        // is origin-bound for free — it posts with `targetOrigin`, so a page\n        // can only ever receive a token minted for *itself*. Delivering\n        // server-side dropped that binding; this restores it. Without it a\n        // popup talked into minting for app X (see `trustsOpenerOriginParam`\n        // in the GUI) hands X's token to whoever holds the session id.\n        if (!this.#tokenIsForApp(token, expectedAppUid)) {\n            // Deliberately the same 408 the no-token path returns: a caller\n            // learns only that nothing arrived for them, not that a token\n            // for a different app went past.\n            throw new HttpError(408, 'Request timeout.', {\n                legacyCode: 'request_timeout',\n            });\n        }","sourceCodeStart":254,"sourceCodeEnd":290,"githubUrl":"https://github.com/HeyPuter/puter/blob/908ec23eda38526170322c3edf71ba45ecb1ca95/src/backend/controllers/auth/AuthController.ts#L254-L290","documentation":"Returned by POST /login/wait when no auth token arrived on the pubsub.login.{session} channel within the 10-second wait window. The poller is expected to call /login/set from the popup after sign-in; if that never happens (or happens too slowly), the long-poll times out. Clients are designed to retry this endpoint periodically while waiting.","triggerScenarios":"The user has not yet completed sign-in in the popup; the popup's /login/set call failed or was never made; network/event-bus latency exceeded 10s; the session id used by /login/wait differs from the one used by /login/set.","commonSituations":"Normal user is still typing credentials; popup was closed before completing login; mismatched session UUIDs between the polling page and the popup; event bus disruption.","solutions":["Treat the 408 as 'keep waiting' and re-poll /login/wait (the rate limit allows 100 per 15 min per IP).","Verify the popup is still open and using the same session UUID when it posts to /login/set.","If the user closed the popup, stop polling and surface a login-cancelled UI."],"exampleFix":"// before: single poll gives up on timeout\ntry { await fetch('/login/wait', {...}); } catch { throw new Error('login failed'); }\n\n// after: retry until the user cancels\nwhile (!cancelled) {\n  try { const r = await fetch('/login/wait', {...}); if (r.ok) return await r.json(); }\n  catch (e) { if (e.code !== 'request_timeout') throw e; }\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"while (!cancelled) {\n  try {\n    const r = await fetch('/login/wait', { method:'POST', body:JSON.stringify({ session }) });\n    if (r.ok) return await r.json();\n  } catch (e) {\n    if (e.code !== 'request_timeout') throw e;\n  }\n}","preventionTips":["Treat 408 as 'keep polling' — the rate limit budgets 100 polls per 15 min.","Stop polling when the user closes the popup."],"tags":["auth","login","timeout","session","polling"],"backgroundTag":null,"analyzedSha":"908ec23eda38526170322c3edf71ba45ecb1ca95","analyzedAt":"2026-08-12T20:53:15.911Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}