santifer/career-ops · warning · Error

Could not fetch job page: ${e.message}

Error message

Could not fetch job page: ${e.message}

What it means

Outer wrapper thrown by fetchJobPage()'s plain-fetch fallback catch block; it wraps any underlying failure (the HTTP-status error from the inner throw, a DNS failure, a connection reset, or an abort) into a single 'Could not fetch job page' error with the original message preserved. It is the user-facing fetch failure surface.

Source

Thrown at openrouter-runner.mjs:435

      });
      return text.slice(0, 16_000);
    } catch (e) {
      console.warn(`[fetch] Playwright error: ${e.message} — falling back to plain fetch.`);
    } finally {
      if (browser) await browser.close().catch(() => {});
    }
  }

  // Plain HTTP fallback
  try {
    const r = await fetch(url, {
      headers: { 'User-Agent': DEFAULT_USER_AGENT }
    });
    if (!r.ok) throw new Error(`HTTP ${r.status} ${r.statusText}`);
    const html = await r.text();
    return html.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim().slice(0, 16_000);
  } catch (e) {
    throw new Error(`Could not fetch job page: ${e.message}`);
  }
}

// ---------------------------------------------------------------------------
// portals.yml parser — reads the canonical schema with js-yaml (same library and
// field names as scan.mjs: `title_filter.positive/negative` + `tracked_companies`),
// so it never drifts from the main scanner. The runner's no-CLI scan path covers
// companies that expose a direct JSON `api:`; careers_url-only / Playwright /
// search-query companies are handled by the full /career-ops scan pipeline.
// `rawOverride` lets tests feed YAML text directly (see test-all.mjs drift guard).
// ---------------------------------------------------------------------------
function normKeywords(v) {
  if (!Array.isArray(v)) return [];
  return v.map(x => String(x ?? '').toLowerCase().trim()).filter(Boolean);
}

export function parsePortals(rawOverride) {
  const raw = rawOverride ?? readFile('portals.yml');

View on GitHub (pinned to 9b17a8ac97)

Solutions

  1. Read the inner message after the colon — it carries the real cause (HTTP code, ENOTFOUND, ECONNRESET, etc.).
  2. If the inner cause is a connection/DNS error, confirm connectivity to the host from a browser or curl.
  3. If the inner cause is HTTP status, follow the fix for error 82.
  4. Install playwright so the browser path is attempted before this fallback.
  5. Mark the pipeline entry dead if the page is genuinely unreachable.

Example fix

// before
throw new Error(`Could not fetch job page: ${e.message}`);
// after: surface the status code so callers can branch
const cause = e.status ? ` (HTTP ${e.status})` : `: ${e.message}`;
throw new Error(`Could not fetch job page${cause}`);
Defensive patterns

Strategy: try-catch

Validate before calling

null

Type guard

null

Try / catch

try {
  const html = await fetchJobPage(url);
} catch (e) {
  // e.message starts with 'Could not fetch job page:'; the cause is after the colon
  console.error('fetch failed:', e.message);
  // decide: retry (transient), drop (404), or escalate
}

Prevention

When it happens

Trigger: Any fetch failure in the plain-fetch fallback path: non-2xx (wrapped from error 82), ENOTFOUND/ECONNREFUSED/ECONNRESET, TLS certificate error, or AbortError from a timeout. It fires after Playwright either is unavailable or also failed.

Common situations: No network/DNS for the host; corporate firewall blocks the ATS; expired TLS cert; the host is up but resets the connection due to bot detection; Playwright missing AND remote is down.

Related errors


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