{"record":{"id":"04ecb614ba9d8044","repo":"santifer/career-ops","slug":"apply-session-not-found","errorCode":null,"errorMessage":"apply session not found","messagePattern":"apply session not found","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"web/src/lib/apply/session.ts","lineNumber":524,"sourceCode":"  const endPath = (() => {\n    try {\n      return new URL(s.frame.url()).pathname;\n    } catch {\n      return s.frame.url();\n    }\n  })();\n  // Read the real form back: did every answer actually land? any validation\n  // error? — so we warn the user about silent divergence before the handoff.\n  const issues = await verifyFill(s.frame, fieldsMeta, answers).catch(() => [] as ApplyIssue[]);\n  return { steps, navigated: endPath !== startPath, issues };\n}\n\n/** Hand the real (now pre-filled) form to the HUMAN to review + submit. The\n *  window was kept OFF-SCREEN during fill, so bringToFront alone wouldn't make it\n *  visible — we reposition it on-screen via CDP first. We never submit. */\nexport async function handoffSession(id: string): Promise<void> {\n  const s = SESSIONS.get(id);\n  if (!s) throw new Error(\"apply session not found\");\n  try {\n    const cdp = await s.context.newCDPSession(s.page);\n    const { windowId } = (await cdp.send(\"Browser.getWindowForTarget\")) as { windowId: number };\n    await cdp.send(\"Browser.setWindowBounds\", {\n      windowId,\n      bounds: { left: 80, top: 60, width: 1280, height: 920, windowState: \"normal\" },\n    });\n    await cdp.detach().catch(() => {});\n  } catch {\n    /* CDP unavailable → bringToFront still raises it */\n  }\n  await s.page.bringToFront().catch(() => {});\n}\n","sourceCodeStart":506,"sourceCodeEnd":538,"githubUrl":"https://github.com/santifer/career-ops/blob/9b17a8ac97b398a496b38e423ae24e433b43254f/web/src/lib/apply/session.ts#L506-L538","documentation":"Thrown by `handoffSession` when the requested `id` is missing from the `SESSIONS` map — same root cause as the fillSession case but at the final 'hand the filled form to the human' step. After filling, the code repositions the browser window on-screen via CDP and calls `bringToFront`; both require a live session, so a missing one is fatal before any UI work begins.","triggerScenarios":"Calling `handoffSession(id)` after the session was pruned, after the process restarted, with a typo'd id, or after the session was already handed off / closed. The CDP window-reposition and `bringToFront` calls never execute because the guard fails first.","commonSituations":"User left the filled form open overnight (idle pruner cleared it); dev server restart lost in-memory state; UI re-issues handoff after a successful one; double-click on a 'review & submit' button; session closed elsewhere in a multi-tab flow.","solutions":["Reopen the session (`openSession`) and re-`fillSession` before retrying handoff — handoff is meaningless without a filled, live form.","Surface this in the UI as 'your session expired — please reopen the application' rather than a raw error.","Chain open → fill → handoff as one continuous flow with no long pauses so the idle pruner can't fire.","Track whether handoff already succeeded to make the operation idempotent and prevent duplicate triggers.","If idle expiry is too aggressive for your users, extend the prune threshold."],"exampleFix":"// before\nawait handoffSession(staleId); // throws 'apply session not found'\n// after — ensure a live, filled session first\nconst s = SESSIONS.has(id) ? { id } : await openSession(url);\nawait fillSession(s.id, answers, fields);\nawait handoffSession(s.id);","handlingStrategy":"retry","validationCode":"// Only hand off a known-live, filled session.\nasync function safeHandoff(id, url, answers, fields) {\n  if (!SESSIONS.has(id)) {\n    const fresh = await openSession(url);\n    await fillSession(fresh.id, answers, fields);\n    return handoffSession(fresh.id);\n  }\n  return handoffSession(id);\n}","typeGuard":null,"tryCatchPattern":"try { await handoffSession(id); }\ncatch (e) {\n  if (/not found/i.test(e.message)) {\n    const fresh = await openSession(url);\n    await fillSession(fresh.id, answers, fields);\n    await handoffSession(fresh.id);\n  } else throw e;\n}","preventionTips":["Make handoff idempotent: track a 'handedOff' flag per id and skip if already done.","Open → fill → handoff in one continuous flow.","On dev server hot-reload, clear UI references to stale session ids.","Surface 'session expired — reopen' to the user rather than the raw error."],"tags":["session","expiry","state","apply","handoff"],"backgroundTag":null,"analyzedSha":"9b17a8ac97b398a496b38e423ae24e433b43254f","analyzedAt":"2026-08-13T00:48:39.135Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}