{"record":{"id":"c6f1e076687fe8c0","repo":"sipeed/picoclaw","slug":"flow-not-found","errorCode":null,"errorMessage":"flow not found","messagePattern":"flow not found","errorType":"http","errorClass":null,"httpStatus":404,"severity":"warning","filePath":"web/backend/api/oauth.go","lineNumber":330,"sourceCode":"\t\t\t\"auth_url\":   authURL,\n\t\t\t\"expires_at\": flow.ExpiresAt.Format(time.RFC3339),\n\t\t})\n\t\treturn\n\tdefault:\n\t\thttp.Error(w, \"unsupported login method\", http.StatusBadRequest)\n\t}\n}\n\nfunc (h *Handler) handleGetOAuthFlow(w http.ResponseWriter, r *http.Request) {\n\tflowID := strings.TrimSpace(r.PathValue(\"id\"))\n\tif flowID == \"\" {\n\t\thttp.Error(w, \"missing flow id\", http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tflow, ok := h.getOAuthFlow(flowID)\n\tif !ok {\n\t\thttp.Error(w, \"flow not found\", http.StatusNotFound)\n\t\treturn\n\t}\n\n\tw.Header().Set(\"Content-Type\", \"application/json\")\n\t_ = json.NewEncoder(w).Encode(flowToResponse(flow))\n}\n\nfunc (h *Handler) handlePollOAuthFlow(w http.ResponseWriter, r *http.Request) {\n\tflowID := strings.TrimSpace(r.PathValue(\"id\"))\n\tif flowID == \"\" {\n\t\thttp.Error(w, \"missing flow id\", http.StatusBadRequest)\n\t\treturn\n\t}\n\n\tflow, ok := h.getOAuthFlow(flowID)\n\tif !ok {\n\t\thttp.Error(w, \"flow not found\", http.StatusNotFound)\n\t\treturn","sourceCodeStart":312,"sourceCodeEnd":348,"githubUrl":"https://github.com/sipeed/picoclaw/blob/49183d7e8daed0dba89ddbb6fcb60089401d9680/web/backend/api/oauth.go#L312-L348","documentation":"Returned as HTTP 404 by GET /api/oauth/flows/{id} when no flow with that id exists in the handler's in-memory map. Flows live only in process memory: pending flows expire after 10 minutes (browser) or 15 minutes (device code), and terminal flows (success/error/expired) are garbage-collected 30 minutes after they finish. A backend restart wipes all flows.","triggerScenarios":"GET /api/oauth/flows/abc123 where the id was never issued; the flow expired and was GC'd; the flow finished more than 30 minutes ago; or the backend process restarted after login started (e.g. hot reload, container restart, redeploy).","commonSituations":"Leaving the login wizard open in a browser tab overnight then resuming; dev-mode auto-reload restarting the Go binary mid-flow; polling with a flow_id from a previous session; multi-replica setups where the GET lands on a different process than the one holding the flow.","solutions":["Start a fresh login (POST /api/oauth/login) and use the newly returned flow_id — expired/completed flows cannot be recovered.","Poll within the flow TTL: respect the expires_at field returned with the flow.","Keep the backend process alive during an interactive login; if it restarted, any old flow_id is invalid.","If you scale the web backend, ensure sticky routing or a single instance owns OAuth flows, since they are in-memory."],"exampleFix":"// before: reusing a stale id after backend restart\nconst flow = await fetch(`/api/oauth/flows/${oldFlowId}`).then(r => r.json()); // 404 flow not found\n\n// after: restart the flow when 404\nlet flow = await fetch(`/api/oauth/flows/${flowId}`);\nif (flow.status === 404) {\n  const login = await fetch('/api/oauth/login', {method:'POST', body: JSON.stringify({provider, method})}).then(r => r.json());\n  flowId = login.flow_id;\n  flow = await fetch(`/api/oauth/flows/${flowId}`);\n}","handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":"const res = await fetch(`/api/oauth/flows/${flowId}`);\nif (res.status === 404) {\n  // flow expired, GC'd, or backend restarted: restart login instead of retrying the GET\n  ({ flow_id: flowId } = await startLogin(provider, method));\n  return pollFlow(flowId);\n}\nreturn res.json();","preventionTips":["Complete the login within the flow TTL (10 min browser, 15 min device code) using the returned expires_at.","Keep the backend process alive during interactive logins; restarts wipe in-memory flows.","Persist the flow_id from the login response and stop querying flows after they reach a terminal status."],"tags":["oauth","http-404","flow","expiry","in-memory-state"],"backgroundTag":null,"analyzedSha":"49183d7e8daed0dba89ddbb6fcb60089401d9680","analyzedAt":"2026-08-15T21:55:41.315Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}