santifer/career-ops · error · Error
gem: JobBoardList failed: ${listResult.errors[0]?.message ||
Error message
gem: JobBoardList failed: ${listResult.errors[0]?.message || 'unknown GraphQL error'} What it means
gem.mjs throws this after the batched GraphQL POST returns, when the first element of the response array carries a non-empty errors[] array. The Gem batch endpoint returns one result object per operation; for the JobBoardList operation an errors entry means the GraphQL layer rejected the query — most often an invalid/unknown boardId, a board that is disabled/private, or a schema/operation change upstream.
Source
Thrown at providers/gem.mjs:170
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'}`);
}
const postings = listResult?.data?.oatsExternalJobPostings?.jobPostings;
if (!Array.isArray(postings)) return [];
const validPostings = postings.filter(/** @param {any} p */ p => p.extId && p.title);
// Enrichment, not core data — postedAt/description matter but their
// absence shouldn't fail the whole board. One extra batched POST (one
// ExternalJobPostingQuery op per job) rather than N round-trips.
const postedAtByExtId = new Map();
const descriptionByExtId = new Map();
if (validPostings.length > 0) {
try {
const detailBody = JSON.stringify(
validPostings.map(/** @param {any} p */ p => ({
operationName: 'ExternalJobPostingQuery',
variables: { boardId, extId: p.extId },
query: JOB_DETAIL_QUERY,View on GitHub (pinned to 9b17a8ac97)
Solutions
- Read errors[0].message from the thrown text — it usually states the exact GraphQL problem (e.g. 'board not found', field deprecation).
- Confirm the boardId by opening https://jobs.gem.com/<boardId> in a browser; if it 404s, update careers_url to the correct board or disable the entry.
- If the board is genuinely gone/private, set enabled: false or remove the entry.
- If the message indicates a schema/operation change, re-capture the current JobBoardList query from a live browser session and update JOB_BOARD_LIST_QUERY.
Example fix
# before - name: Acme provider: gem careers_url: https://jobs.gem.com/acme-old # board disabled -> GraphQL error # after - name: Acme provider: gem careers_url: https://jobs.gem.com/acme # correct, live board id
Defensive patterns
Strategy: try-catch
Type guard
// Detect a GraphQL errors[] payload in a Gem batch result.
function gemListHasErrors(json) {
const first = Array.isArray(json) ? json[0] : null;
return !!first && Array.isArray(first.errors) && first.errors.length > 0;
} Try / catch
// Distinguish a gone-private board (disable entry) from a transient/schema error (surface).
try {
const jobs = await gemProvider.fetch(entry, ctx);
} catch (err) {
const m = /gem: JobBoardList failed: (.*)/.exec(err.message);
if (m && /not found|board/i.test(m[1])) {
console.error(`gem board for ${entry.name} unavailable: ${m[1]} — disabling entry`);
return [];
}
throw err;
} Prevention
- Periodically re-verify each Gem boardId still resolves in a browser.
- Capture errors[0].message in logs so a schema change is distinguishable from a per-board problem.
- Keep JOB_BOARD_LIST_QUERY aligned with a recently captured live browser request.
When it happens
Trigger: The boardId derived from careers_url is wrong or no longer published (board taken private/deleted); Gem renamed the JobBoardList operation or the oatsExternalJobPostings field and the shipped query no longer compiles server-side; the board requires auth/cookies that the public endpoint no longer grants; a transient server-side error surfaced in errors[0].message.
Common situations: A company migrated off Gem or made its board private; the board id in portals.yml was mistyped; Gem shipped a schema bump (the query is reverse-engineered, per the file header); rate-limiting/abuse detection returned a GraphQL error instead of an HTTP 4xx.
Related errors
- glints: unexpected API response — ${JSON.stringify(json).sli
- flowxtra: unexpected API response on page ${page} — expected
- gem: cannot derive board id for ${entry.name}
- getonbrd: unexpected API response on page ${page} — expected
- glints: HTTP ${err.status} — ${detail}
AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13).
Data as JSON: /api/errors/76f3c74b22942c80.
Report an issue: GitHub.