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

  1. Read errors[0].message from the thrown text — it usually states the exact GraphQL problem (e.g. 'board not found', field deprecation).
  2. 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.
  3. If the board is genuinely gone/private, set enabled: false or remove the entry.
  4. 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

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


AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13). Data as JSON: /api/errors/76f3c74b22942c80. Report an issue: GitHub.