santifer/career-ops · error · Error
gem: invalid URL: ${url}
Error message
gem: invalid URL: ${url} What it means
gem.mjs throws this inside assertGemUrl() when `new URL(url)` raises — i.e. the string is not an absolute, parseable URL. The guard runs before every POST to the Gem GraphQL batch endpoint. In production the validated value is the module constant GEM_API_URL ('https://jobs.gem.com/api/public/graphql/batch'), so a throw here means that constant was corrupted/injected or a test called assertGemUrl() with a relative/malformed string.
Source
Thrown at providers/gem.mjs:111
// to before this field list was widened.
/** @param {any} posting */
function buildJobDescriptionText(posting) {
const intro = htmlToText(posting?.jobPostSectionHtml?.introHtml);
const body = htmlToText(posting?.descriptionHtml);
const outro = htmlToText(posting?.jobPostSectionHtml?.outroHtml);
const compensation = htmlToText(posting?.compensationHtml);
const text = [intro, body, outro].filter(Boolean).join('\n\n');
return compensation ? [text, `Compensation: ${compensation}`].filter(Boolean).join('\n\n') : text;
}
/** @param {string} url */
function assertGemUrl(url) {
let parsed;
try {
parsed = new URL(url);
} catch {
throw new Error(`gem: invalid URL: ${url}`);
}
if (parsed.protocol !== 'https:') throw new Error(`gem: URL must use HTTPS: ${url}`);
if (!ALLOWED_GEM_HOSTS.has(parsed.hostname))
throw new Error(`gem: untrusted hostname "${parsed.hostname}" — must be one of: ${[...ALLOWED_GEM_HOSTS].join(', ')}`);
return url;
}
/** @param {import('./_types.js').PortalEntry} entry */
function resolveBoardId(entry) {
const raw = typeof entry.careers_url === 'string' ? entry.careers_url : '';
if (!raw) return null;
let parsed;
try {
parsed = new URL(raw);
} catch {
return null;
}
if (parsed.hostname !== 'jobs.gem.com') return null;View on GitHub (pinned to 9b17a8ac97)
Solutions
- Restore GEM_API_URL to the full absolute form 'https://jobs.gem.com/api/public/graphql/batch'.
- If you meant to test the guard, pass an absolute-but-invalid string (e.g. 'https://') only when you intend to assert the throw.
- Audit any code that builds/mutates GEM_API_URL at runtime — it is meant to be a literal constant.
Example fix
// before const GEM_API_URL = '/api/public/graphql/batch'; // relative -> new URL() throws // after const GEM_API_URL = 'https://jobs.gem.com/api/public/graphql/batch';
Defensive patterns
Strategy: validation
Validate before calling
// Startup self-check: GEM_API_URL must be an absolute URL.
function checkGemApiUrl() {
try { new URL('https://jobs.gem.com/api/public/graphql/batch'); }
catch { throw new Error('GEM_API_URL is not a valid absolute URL'); }
} Try / catch
// Defensive: validate before the first request so the error surfaces with context.
try { assertGemUrl(GEM_API_URL); }
catch (err) { throw new Error(`gem endpoint misconfigured: ${err.message}`); } Prevention
- Keep GEM_API_URL as a string literal; never build it from concatenation of env vars.
- Add a CI unit test asserting assertGemUrl(GEM_API_URL) does not throw.
- Review any PR that touches the GEM_API_URL line.
When it happens
Trigger: GEM_API_URL was edited to a relative path or a string with illegal characters (spaces, stray quotes); an env var or config merge injected an empty/undefined-coerced string; a test invokes assertGemUrl('/api/graphql/batch') or assertGemUrl(undefined).
Common situations: A contributor changes the endpoint to a path-only string ('/api/public/graphql/batch') thinking it will be resolved against a base; a templating/CI step mangled the constant; a unit test passes a deliberately bad URL but the test expected a different message.
Related errors
- gem: URL must use HTTPS: ${url}
- gem: untrusted hostname "${parsed.hostname}" — must be one o
- flowxtra: untrusted hostname "${parsed.hostname}" — must be
- getonbrd: invalid URL: ${url}
- getonbrd: URL must use HTTPS: ${url}
AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13).
Data as JSON: /api/errors/d92c596ba9b88dae.
Report an issue: GitHub.