santifer/career-ops · error · Error
gem: cannot derive board id for ${entry.name}
Error message
gem: cannot derive board id for ${entry.name} What it means
gem.mjs throws this at the top of fetch() when resolveBoardId(entry) returns null. resolveBoardId extracts the board id from entry.careers_url: it must be a parseable URL whose hostname is exactly jobs.gem.com and whose first path segment is the board id. If careers_url is missing, not a jobs.gem.com URL, or has no path segment, no board id can be derived and the provider refuses to fetch.
Source
Thrown at providers/gem.mjs:153
function formatLocation(loc) {
const parts = [];
if (typeof loc?.name === 'string' && loc.name.trim()) parts.push(loc.name.trim());
if (loc?.isRemote) parts.push('Remote');
return parts.join(' · ');
}
/** @type {Provider} */
export default {
id: 'gem',
detect(entry) {
const boardId = resolveBoardId(entry);
return boardId ? { url: `${GEM_API_URL}?board=${boardId}` } : null;
},
async fetch(entry, ctx) {
const boardId = resolveBoardId(entry);
if (!boardId) throw new Error(`gem: cannot derive board id for ${entry.name}`);
assertGemUrl(GEM_API_URL);
const body = JSON.stringify([
{ operationName: 'JobBoardList', variables: { boardId }, query: JOB_BOARD_LIST_QUERY },
]);
// redirect:'error' prevents SSRF via server-side redirects; combined with
// assertGemUrl above it guarantees the final hostname stays in the allowlist.
const json = /** @type {any} */ (await ctx.fetchJson(GEM_API_URL, {
method: 'POST',
headers: { 'content-type': 'application/json', batch: 'true' },
body,
redirect: 'error',
}));
const listResult = json?.[0];
if (Array.isArray(listResult?.errors) && listResult.errors.length > 0) {
throw new Error(`gem: JobBoardList failed: ${listResult.errors[0]?.message || 'unknown GraphQL error'}`);
}View on GitHub (pinned to 9b17a8ac97)
Solutions
- Open the company's Gem board in a browser and copy the URL whose host is jobs.gem.com and whose first path segment is the board id (e.g. https://jobs.gem.com/acme).
- Set careers_url to that URL in portals.yml for the provider: gem entry.
- If you only know a non-Gem careers page, do not use provider: gem — pick the provider that actually backs that URL.
- Verify there are no leading/trailing spaces or quotes around careers_url in the YAML.
Example fix
# before - name: Acme provider: gem careers_url: https://acme.com/careers # not a Gem board -> throws # after - name: Acme provider: gem careers_url: https://jobs.gem.com/acme
Defensive patterns
Strategy: validation
Validate before calling
// Pre-flight check for a portals.yml entry before invoking the gem provider.
function gemEntryIsValid(entry) {
if (entry.provider !== 'gem') return true;
const raw = typeof entry.careers_url === 'string' ? entry.careers_url : '';
if (!raw) return false;
let u;
try { u = new URL(raw); } catch { return false; }
if (u.hostname !== 'jobs.gem.com') return false;
return /^\/[^/?#]+/.test(u.pathname); // has a board id segment
} Prevention
- Always source the Gem board URL from the browser address bar of the live jobs.gem.com/<boardId> page.
- Validate portals.yml entries with a lint pass that calls gemEntryIsValid for every provider: gem row.
- Prefer detect() (which returns null on a bad entry) over fetch() when only probing.
When it happens
Trigger: A portals.yml entry has provider: gem but no careers_url; careers_url points at the company's own careers site instead of jobs.gem.com; careers_url is a jobs.gem.com board but written as a deep link (e.g. https://jobs.gem.com/board/123) where the segment is still captured but if the URL is malformed/empty it returns null; the hostname has a trailing slash variant or www prefix not equal to jobs.gem.com.
Common situations: Operator adds a Gem-hosted company but copies the careers page URL (e.g. https://company.com/careers) rather than the jobs.gem.com/<boardId> URL; the entry was auto-generated with a placeholder careers_url; the Gem board URL was typed with a typo in the host.
Related errors
- getro: ${entry.name} needs a numeric 'getro_collection' in p
- vdab: entry "${entry.name || '(unnamed)'}" has no vdab.keywo
- wttj: the WTTJ board is global — configure explicit searches
- Unsupported profile photo style: ${style} (expected rounded,
- Malformed benchmarks file at ${path}: expected a top-level "
AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13).
Data as JSON: /api/errors/1e4fdef6455257c2.
Report an issue: GitHub.