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
- Read the full `msg` value in the thrown error — it is caller-supplied and names the concrete sub-failure; address that specific cause first.
- 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.
- If the site is just slow, raise `context.setDefaultTimeout` (currently 8000ms) or pass a longer `waitUntil` option to `page.goto`.
- Check network/proxy: ensure outbound HTTPS works from the process environment (`curl -I <url>`).
- 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
- Validate the URL (reachable + HTML) before invoking openSession.
- Increase `context.setDefaultTimeout` if your target ATS is reliably slow.
- Detect auth-wall / CAPTCHA responses upstream and route the user to manual apply.
- Log the full `msg` so the specific abort reason is always visible.
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
- The apply feature needs Google Chrome. Install Chrome (or ru
- Image failed to decode within 10s (unreadable or corrupt fil
- apply session not found (it may have expired)
- apply session not found
- LOCK_TIMEOUT
AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13).
Data as JSON: /api/errors/5e125c374a638746.
Report an issue: GitHub.