santifer/career-ops · error · Error

themuse: invalid URL

Error message

themuse: invalid URL: ${url}

What it means

assertMuseUrl() is the provider's URL validator: it first parses the input with the URL constructor. If parsing fails entirely (malformed URL string), it throws this error before any protocol or host checks. It guards every URL the provider uses so downstream fetches never see unparseable input.

Solutions

  1. Fix the URL in the config so it is absolute and well-formed, e.g. 'https://www.themuse.com/api/v2/jobs?page=0'.
  2. Trim whitespace and replace smart quotes with plain ASCII quotes in the config value.
  3. Log the exact offending string (it is included in the message) and run new URL(value) in a REPL to see the syntax failure.
  4. If the value is built dynamically, check the interpolation for undefined/empty parts.

Example fix

// before
url: themuse.com/api/v2/jobs
// after
url: https://www.themuse.com/api/v2/jobs
Defensive patterns

Strategy: validation

Validate before calling

function isParseableUrl(value) {
  if (typeof value !== 'string' || value.trim() === '') return false;
  try { new URL(value.trim()); return true; } catch { return false; }
}
// run before invoking the provider: if (!isParseableUrl(entry.url)) fix config first

Type guard

const isUrl = (v) =>
  typeof v === 'string'
  && v.length > 0
  && (() => { try { new URL(v); return true; } catch { return false; } })();

Try / catch

try {
  return await provider.fetch(entry, ctx);
} catch (err) {
  if (String(err.message).startsWith('themuse: invalid URL')) {
    console.error(`Config fix needed for ${entry.name}: ${err.message}`);
    return null;
  }
  throw err;
}

Prevention

When it happens

Trigger: assertMuseUrl(url) is invoked with a string that new URL() rejects — missing scheme ('themuse.com/jobs'), spaces, invalid characters, an empty string, or a non-string coerced badly.

Common situations: A portals.yml entry with a careers_url missing the 'https://' prefix; trailing whitespace or smart quotes pasted from a document; an interpolated template producing an empty or partially-built URL.

Understand the failure class

Background: "Invalid URL" / "URL cannot be empty": fix the malformed or missing URL behind request-construction failures — this error's family across 50 libraries.

Related errors


AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16). Data as JSON: /api/errors/07c2b10ad85e51b0. Report an issue: GitHub.

Appendix: source

Thrown at providers/themuse.mjs:88

      const backoff = Math.min(RETRY_BASE_DELAY_MS * 2 ** attempt, RETRY_MAX_DELAY_MS);
      // A server-supplied Retry-After is honored, but still clamped — an
      // unbounded value would otherwise stall this board's fetch for as long
      // as the server says, defeating the point of a bounded backoff.
      const retryAfterMs = parseRetryAfterMs(err?.retryAfter);
      const delayMs = retryAfterMs !== null ? Math.min(retryAfterMs, RETRY_MAX_DELAY_MS * 4) : (backoff + Math.random() * 250);
      await sleep(delayMs, ctx);
    }
  }
  throw lastErr;
}

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

/**
 * Normalize a single result from the Muse API response. Exported for unit tests.
 *
 * Field mapping:
 *   name              → title
 *   refs.landing_page → url
 *   company.name      → company
 *   locations[0].name → location
 *
 * Returns null when required fields (title or url) are missing or invalid.

View on GitHub (pinned to aac998c7ed)