santifer/career-ops · error · Error

${msg}

Error message

${msg}

What it means

A generic, parameterized failure (`throw new Error(msg)`) raised inside the `abort` closure within `openSession`. `abort` is the single error path for any unrecoverable problem while opening a real application URL in the headed browser — it closes the context, schedules idle cleanup, and re-throws the supplied message. The `${msg}` placeholder means the actual cause is composed by the caller (navigation failure, page crash, timeout, missing form, etc.).

Source

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

async function nudgeScroll(page: Page): Promise<void> {
  for (let i = 1; i <= 3; i++) {
    await page.evaluate((y) => window.scrollTo(0, y), i * 1200).catch(() => {});
    await page.waitForTimeout(250);
  }
  await page.evaluate(() => window.scrollTo(0, 0)).catch(() => {});
}

export async function openSession(url: string, cliId?: string, forceAgent?: boolean, noApplyBtn?: boolean): Promise<{ id: string; title: string; fields: ApplyField[]; shots: string[]; issues: ApplyIssue[]; needsDrive?: boolean }> {
  prune();
  if (globalThis.__coIdleTimer) clearTimeout(globalThis.__coIdleTimer); // someone's active
  const browser = await headedBrowser();
  const context = await browser.newContext({ viewport: { width: 1280, height: 900 } });
  context.setDefaultTimeout(8000); // no single action hangs the whole open/fill
  const page = await context.newPage();
  const abort = async (msg: string): Promise<never> => {
    await context.close().catch(() => {});
    if (SESSIONS.size === 0) scheduleIdleClose();
    throw new Error(msg);
  };
  // Capture the real form as we read it → a "behind the scenes" progress strip
  // that proves we genuinely opened + parsed THEIR form (not magic). The last
  // shot doubles as a subtle blurred backdrop behind the clean proxy.
  const shots: string[] = [];
  const snap = async () => {
    try {
      const b = await page.screenshot({ type: "jpeg", quality: 42 });
      shots.push(`data:image/jpeg;base64,${b.toString("base64")}`);
    } catch {
      /* ignore */
    }
  };
  // 1) Navigate (resilient) → cheapest hard-block check on the Response status.
  const resp = await gotoResilient(page, url);
  await snap(); // first paint
  const sBlock = statusBlock(resp?.status(), resp ? resp.headers() : {});
  if (sBlock) return abort(sBlock.message);

View on GitHub (pinned to 9b17a8ac97)

Solutions

  1. Read the full `msg` value in the thrown error — it is caller-supplied and names the concrete sub-failure; address that specific cause first.
  2. Confirm the URL opens in a normal Chrome tab; if it needs login or throws a CAPTCHA, the apply feature cannot drive it — use manual `apply` instead.
  3. If the site is just slow, raise `context.setDefaultTimeout` (currently 8000ms) or pass a longer `waitUntil` option to `page.goto`.
  4. Check network/proxy: ensure outbound HTTPS works from the process environment (`curl -I <url>`).
  5. If the site blocks automation, fall back to filling the form yourself with the prepared answers/PDF and use `handoffSession` only for the human review step.

Example fix

// before
context.setDefaultTimeout(8000);
await page.goto(url);
// after — give slow ATS pages more room
context.setDefaultTimeout(20000);
await page.goto(url, { waitUntil: 'domcontentloaded' });
Defensive patterns

Strategy: try-catch

Validate before calling

// Validate the URL is reachable before handing it to openSession.
async function isReachable(url) {
  try {
    const res = await fetch(url, { method: 'GET', redirect: 'follow' });
    const ct = res.headers.get('content-type') || '';
    return res.ok && ct.includes('text/html');
  } catch { return false; }
}

Try / catch

try {
  const session = await openSession(url, cliId);
} catch (e) {
  // e.message is the caller-supplied abort reason; classify by content
  if (/timeout|net::ERR|navigation/i.test(e.message)) {
    showUser('Could not open the form at that URL. Check the link, login wall, or CAPTCHA.');
  } else { showUser(e.message); }
}

Prevention

When it happens

Trigger: Any call to `abort(msg)` inside `openSession`: the target URL is unreachable / DNS failure / returns non-HTML; `page.goto` exceeds the 8000ms `setDefaultTimeout`; the page closes or crashes mid-load; a selector the open-flow depends on (e.g. form detection) never appears and the code calls `abort('...')` with a descriptive reason. The screenshot snapshot loop itself swallows errors, so this is only explicit aborts.

Common situations: Pasting a JD URL behind authentication or a paywall; URL with a typo / wrong scheme; corporate proxy blocking the outbound navigation; extremely slow site exceeding 8s; ATS that redirects to a login page; anti-bot wall (Cloudflare) blocking the automated browser; ATS site redesign removing the expected form container.

Related errors


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