santifer/career-ops · error · Error

careerviet: invalid URL: ${url}

Error message

careerviet: invalid URL: ${url}

What it means

The CareerViet provider is hard-pinned to a single trusted host, careerviet.vn (TRUSTED_HOST). assertCareerVietUrl first attempts new URL(url); a string that cannot parse as an absolute URL throws this error before any other validation. Unlike bamboohr/breezy which allow tenant subdomains, careerviet accepts only the exact apex host, but URL well-formedness is checked first.

Source

Thrown at providers/careerviet.mjs:106

 * The "Cập nhật" (updated) date sits in its own <li>, sibling to "Hạn nộp"
 * (deadline) inside the same .time block — the label text is what tells them
 * apart; both wrap their <time> the same way.
 */
const UPDATED_DATE_RE = /Cập nhật(?:<!--[\s\S]*?-->)?\s*:?\s*(?:<\/span>)?\s*<time>([\d/-]+)<\/time>/i;

/** @param {any} ctx @param {number} ms */
function sleep(ctx, ms) {
  if (typeof ctx?.sleep === 'function') return ctx.sleep(ms);
  return new Promise((r) => setTimeout(r, ms));
}

/** @param {string} url */
function assertCareerVietUrl(url) {
  let parsed;
  try {
    parsed = new URL(url);
  } catch {
    throw new Error(`careerviet: invalid URL: ${url}`);
  }
  if (parsed.protocol !== 'https:') throw new Error(`careerviet: URL must use HTTPS: ${url}`);
  if (parsed.hostname !== TRUSTED_HOST) {
    throw new Error(`careerviet: untrusted hostname "${parsed.hostname}" — must be ${TRUSTED_HOST}`);
  }
  return url;
}

/**
 * Collapse a markup fragment to its visible text.
 * @param {string} fragment
 * @returns {string}
 */
export function visibleText(fragment) {
  return decodeEntities(
    String(fragment ?? '')
      .replace(/<!--[\s\S]*?-->/g, ' ')
      .replace(/<[^>]+>/g, ' '),

View on GitHub (pinned to 1696bec4d0)

Solutions

  1. Make the value an absolute URL including the scheme: https://careerviet.vn/viec-lam
  2. Trim whitespace and verify no invisible characters broke the URL string
  3. Inspect the interpolated URL in the error message — 'undefined' or '' points to an unset variable upstream
  4. In code, construct URLs via new URL(path, 'https://careerviet.vn') so well-formedness is guaranteed

Example fix

// before
careers_url: careerviet.vn/viec-lam
// after
careers_url: https://careerviet.vn/viec-lam
Defensive patterns

Strategy: validation

Validate before calling

function isParseableUrl(u) {
  if (typeof u !== 'string' || !u.trim()) return false;
  try { new URL(u); return true; } catch { return false; }
}
if (!isParseableUrl(entry.careers_url)) throw new Error(`bad careerviet URL for ${entry.name}: ${JSON.stringify(entry.careers_url)}`);

Type guard

function parseUrlOrNothing(value) {
  if (typeof value !== 'string') return null;
  try { return new URL(value); } catch { return null; }
}

Try / catch

try {
  await provider.fetch(entry, ctx);
} catch (err) {
  if (String(err.message).includes('invalid URL')) {
    console.error(`Unparseable careerviet URL for ${entry.name} — add the https:// scheme and check variables`, err.message);
  } else throw err;
}

Prevention

When it happens

Trigger: Calling the provider or assertCareerVietUrl with a non-URL string: a bare domain like 'careerviet.vn/viec-lam' without the scheme, an empty string, a string with spaces, or an interpolated variable that is undefined (producing 'undefined').

Common situations: YAML entry missing the https:// prefix; template string where the URL variable was never set; copy-paste from a document that stripped the scheme; building the URL from base + path where base itself was malformed.

Understand the failure class

Background: "Invalid URL" errors: why new URL(), URI.parse, and reqwest::Url reject your string — missing scheme, whitespace, and bad path format — this error's family across 39 libraries.

Related errors


AI-assisted analysis of santifer/career-ops@1696bec4d0 (2026-09-01). Data as JSON: /api/errors/9a43fd1b259276a1. Report an issue: GitHub.