santifer/career-ops · error

gem: cannot derive board id for

Error message

gem: cannot derive board id for ${entry.name}

What it means

The Gem provider has two fetch paths: an explicit REST API URL from the board entry, or a GraphQL JobBoardList call keyed by a board id. When neither an api URL nor a derivable board id exists for the entry, fetch cannot build a request and throws this error naming the entry.

Solutions

  1. Add the board id explicitly to the entry (or the full Gem board URL containing it) so resolveBoardId can extract it.
  2. Configure the entry's api field with Gem's documented REST endpoint URL.
  3. Check the Gem board URL format against what resolveBoardId expects — Gem may have changed their slug/id layout.
  4. Verify the entry is actually a Gem-hosted board; non-Gem boards need a different provider.

Example fix

// before
{ name: 'acme', careers_url: 'https://acme.com/careers' }
// after
{ name: 'acme', careers_url: 'https://acme.com/careers', api: 'https://api.gem.com/v1/boards/acme-123/job_posts' }
Defensive patterns

Strategy: validation

Validate before calling

if (!entry.api && !resolveBoardId(entry)) throw new Error(`Gem entry '${entry.name}' needs an api URL or a board id`);

Type guard

const hasGemTarget = (entry) => Boolean(typeof entry.api === 'string' && entry.api) || Boolean(resolveBoardId(entry));

Try / catch

try { await provider.fetch(ctx, entry); } catch (e) { if (e.message.includes('cannot derive board id')) console.error(`Fix entry '${entry.name}': add its Gem board URL or api field`); throw e; }

Prevention

When it happens

Trigger: A portals.yml Gem entry that has neither an api URL (or resolveRestApiUrl returned empty) nor any field from which resolveBoardId can extract a board id (e.g. board URL path missing the id segment, or the entry only has a generic careers-page URL).

Common situations: Adding a Gem company by pasting only its careers-site URL rather than its Gem board URL; Gem changing their board URL format so the id-extraction logic no longer matches; a partially migrated config entry.

Understand the failure class

Background: "is required", "must be set", "missing required field": configuration validation errors across open-source libraries — this error's family across 36 libraries.

Related errors


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

Appendix: source

Thrown at providers/gem.mjs:199

  detect(entry) {
    const restApiUrl = resolveRestApiUrl(entry);
    if (restApiUrl) return { url: restApiUrl.href };
    const boardId = resolveBoardId(entry);
    return boardId ? { url: `${GEM_API_URL}?board=${boardId}` } : null;
  },

  async fetch(entry, ctx) {
    // Gem documents this unauthenticated REST surface for custom career pages.
    // Keep the existing GraphQL path for jobs.gem.com SPA boards, while
    // allowing operators to pin a verified REST URL from a captured page.
    const restApiUrl = resolveRestApiUrl(entry);
    if (restApiUrl) {
      assertGemUrl(restApiUrl.href);
      const json = /** @type {any} */ (await ctx.fetchJson(restApiUrl.href, { redirect: 'error' }));
      return parseRestResponse(json, entry.name);
    }
    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 aac998c7ed)