santifer/career-ops · error · Error

apply session not found

Error message

apply session not found

What it means

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.

Source

Thrown at web/src/lib/apply/session.ts:524

  const endPath = (() => {
    try {
      return new URL(s.frame.url()).pathname;
    } catch {
      return s.frame.url();
    }
  })();
  // Read the real form back: did every answer actually land? any validation
  // error? — so we warn the user about silent divergence before the handoff.
  const issues = await verifyFill(s.frame, fieldsMeta, answers).catch(() => [] as ApplyIssue[]);
  return { steps, navigated: endPath !== startPath, issues };
}

/** Hand the real (now pre-filled) form to the HUMAN to review + submit. The
 *  window was kept OFF-SCREEN during fill, so bringToFront alone wouldn't make it
 *  visible — we reposition it on-screen via CDP first. We never submit. */
export async function handoffSession(id: string): Promise<void> {
  const s = SESSIONS.get(id);
  if (!s) throw new Error("apply session not found");
  try {
    const cdp = await s.context.newCDPSession(s.page);
    const { windowId } = (await cdp.send("Browser.getWindowForTarget")) as { windowId: number };
    await cdp.send("Browser.setWindowBounds", {
      windowId,
      bounds: { left: 80, top: 60, width: 1280, height: 920, windowState: "normal" },
    });
    await cdp.detach().catch(() => {});
  } catch {
    /* CDP unavailable → bringToFront still raises it */
  }
  await s.page.bringToFront().catch(() => {});
}

View on GitHub (pinned to 9b17a8ac97)

Solutions

  1. Reopen the session (`openSession`) and re-`fillSession` before retrying handoff — handoff is meaningless without a filled, live form.
  2. Surface this in the UI as 'your session expired — please reopen the application' rather than a raw error.
  3. Chain open → fill → handoff as one continuous flow with no long pauses so the idle pruner can't fire.
  4. Track whether handoff already succeeded to make the operation idempotent and prevent duplicate triggers.
  5. If idle expiry is too aggressive for your users, extend the prune threshold.

Example fix

// before
await handoffSession(staleId); // throws 'apply session not found'
// after — ensure a live, filled session first
const s = SESSIONS.has(id) ? { id } : await openSession(url);
await fillSession(s.id, answers, fields);
await handoffSession(s.id);
Defensive patterns

Strategy: retry

Validate before calling

// Only hand off a known-live, filled session.
async function safeHandoff(id, url, answers, fields) {
  if (!SESSIONS.has(id)) {
    const fresh = await openSession(url);
    await fillSession(fresh.id, answers, fields);
    return handoffSession(fresh.id);
  }
  return handoffSession(id);
}

Try / catch

try { await handoffSession(id); }
catch (e) {
  if (/not found/i.test(e.message)) {
    const fresh = await openSession(url);
    await fillSession(fresh.id, answers, fields);
    await handoffSession(fresh.id);
  } else throw e;
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13). Data as JSON: /api/errors/04ecb614ba9d8044. Report an issue: GitHub.