{"record":{"id":"5139bdb4197bf440","repo":"eyaltoledano/claude-task-master","slug":"flow-not-found","errorCode":"FLOW_NOT_FOUND","errorMessage":"Authentication flow expired or not found","messagePattern":"Authentication flow expired or not found","errorType":"error_code","errorClass":"AuthenticationError","httpStatus":404,"severity":"error","filePath":"packages/tm-core/src/modules/auth/services/oauth-service.ts","lineNumber":339,"sourceCode":"\t\t\t);\n\t\t}\n\n\t\twhile (Date.now() - startTime < timeout) {\n\t\t\ttry {\n\t\t\t\tconst response = await fetch(statusUrl, {\n\t\t\t\t\tmethod: 'GET',\n\t\t\t\t\theaders: {\n\t\t\t\t\t\t'User-Agent': `TaskMasterCLI/${this.getCliVersion()}`\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\tif (!response.ok) {\n\t\t\t\t\tconst errorData = (await response.json().catch(() => ({}))) as {\n\t\t\t\t\t\tmessage?: string;\n\t\t\t\t\t};\n\n\t\t\t\t\tif (response.status === 404) {\n\t\t\t\t\t\tthrow new AuthenticationError(\n\t\t\t\t\t\t\t'Authentication flow expired or not found',\n\t\t\t\t\t\t\t'FLOW_NOT_FOUND'\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\n\t\t\t\t\tthrow new AuthenticationError(\n\t\t\t\t\t\terrorData.message || `HTTP ${response.status}`,\n\t\t\t\t\t\t'POLL_FAILED'\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\tconst data = (await response.json()) as FlowStatusResponse;\n\n\t\t\t\tif (!data.success) {\n\t\t\t\t\tthrow new AuthenticationError(\n\t\t\t\t\t\tdata.message || 'Failed to check status',\n\t\t\t\t\t\t'POLL_FAILED'\n\t\t\t\t\t);","sourceCodeStart":321,"sourceCodeEnd":357,"githubUrl":"https://github.com/eyaltoledano/claude-task-master/blob/c0c98d367c55296bfe69e65680625b6db437af02/packages/tm-core/src/modules/auth/services/oauth-service.ts#L321-L357","documentation":"AuthenticationError thrown during polling when the auth server responds with HTTP 404 for the flow-status endpoint, meaning the authentication flow ID is unknown or has expired server-side. The library treats this as terminal — polling will not succeed by continuing, so it aborts immediately.","triggerScenarios":"pollForCompletion receives a 404 from /api/auth/cli/status?flow_id=... because the flow timed out on the server, the server was restarted (in-memory flow store), a load balancer routed the poll to a different instance, or the flowId is wrong/already consumed.","commonSituations":"User taking longer than the server-side flow TTL to authorize in the browser; multi-instance backend without shared session storage; re-running a poll after a completed/expired login; pointing at a different environment's auth server than the one that issued the flowId.","solutions":["Restart the entire authentication flow (start a new PKCE flow) — the old flowId cannot be recovered.","Complete the browser authorization step promptly, within the server's flow TTL.","If self-hosting behind multiple instances, enable shared/sticky storage for CLI auth flows.","Verify baseUrl points to the same environment that issued the flowId (dev vs prod mismatch).","Check server logs for flow_id to confirm whether it expired or was never created."],"exampleFix":"// before: retrying a dead flow\nawait oauth.pollForCompletion(oldFlowId, 60000); // 404 -> FLOW_NOT_FOUND\n// after: catch and restart the flow\ntry {\n  await oauth.pollForCompletion(flowId, 60000);\n} catch (e) {\n  if (e.code === 'FLOW_NOT_FOUND') {\n    const flow = await oauth.startBackendFlow();\n    await oauth.pollForCompletion(flow.flowId, 60000);\n  }\n}","handlingStrategy":"try-catch","validationCode":"// Cannot be predicted client-side; mitigate by completing authorization promptly.\n// Optionally verify the flow is fresh before polling:\nif (Date.now() - flow.startedAt > 5 * 60 * 1000) {\n  flow = await oauth.startBackendFlow(); // flow likely expired server-side\n}","typeGuard":"function isFlowNotFound(e: unknown): e is AuthenticationError {\n  return e instanceof AuthenticationError && (e as any).code === 'FLOW_NOT_FOUND';\n}","tryCatchPattern":"try {\n  await oauth.pollForCompletion(flowId, timeout);\n} catch (e) {\n  if (isFlowNotFound(e)) {\n    // Flow expired or server restarted — start a brand-new login flow\n    const fresh = await oauth.startBackendFlow();\n    await oauth.pollForCompletion(fresh.flowId, timeout);\n  }\n}","preventionTips":["Complete the browser authorization step quickly, within the server's flow TTL","Restart the whole flow instead of retrying an expired flowId","Ensure sticky/shared flow storage when the backend runs multiple instances","Point the CLI at the same environment (dev/prod) that issued the flowId"],"tags":["oauth","http-404","flow-expired","authentication"],"backgroundTag":"auth-flow-expired","analyzedSha":"c0c98d367c55296bfe69e65680625b6db437af02","analyzedAt":"2026-08-29T02:56:26.071Z","schemaVersion":2},"datasetVersion":"2026-08-29T07:17:48.351Z"}