santifer/career-ops · error · Error

tkms: cannot resolve jobs host for

Error message

tkms: cannot resolve jobs host for ${entry.name}

What it means

The tkms provider resolves each portal entry to a concrete jobs-host configuration via resolveConfig(entry). When resolution returns null — the entry doesn't carry the URL fields resolveConfig needs — fetch() refuses to proceed. The provider only knows how to talk to a resolvable TKMS jobs host; it will not guess one from a name.

Solutions

  1. Add or fix the api/careers URL on the entry so resolveConfig() succeeds — check providers/tkms.mjs for the exact expected shape.
  2. Compare your entry against a known-good TKMS entry in portals.yml and copy its field layout.
  3. Confirm the company still uses a TKMS jobs host by opening their careers page; if they migrated ATS, switch the entry to the matching provider.
  4. Log resolveConfig(entry) inputs in a quick REPL to see which field is missing or malformed.

Example fix

// before (portals.yml — no resolvable host)
- name: SomeCompany
  provider: tkms
// after
- name: SomeCompany
  provider: tkms
  careers_url: https://somecompany.com/api/jobs
Defensive patterns

Strategy: validation

Validate before calling

function tkmsEntryIsConfigured(entry) {
  const url = entry.api ?? entry.careers_url;
  return typeof url === 'string' && /^https:\/\//.test(url);
}
// pre-flight: portals.filter(e => e.provider === 'tkms' && !tkmsEntryIsConfigured(e)) → fix config

Type guard

const hasResolvableTkmsHost = (entry) =>
  entry != null
  && typeof entry.api === 'string'
  && entry.api.startsWith('https://');

Try / catch

try {
  return await tkmsProvider.fetch(entry, ctx);
} catch (err) {
  if (String(err.message).startsWith('tkms: cannot resolve jobs host')) {
    console.warn(`${entry.name}: missing/invalid TKMS api URL — fix portals.yml and re-run`);
    return null;
  }
  throw err;
}

Prevention

When it happens

Trigger: fetch(entry, ctx) runs resolveConfig(entry) and gets null: the entry lacks a valid api/careers URL string (resolveConfig({ api: url }) fails) or the expected config fields are missing/mistyped, so it throws 'cannot resolve jobs host for <entry.name>'.

Common situations: A portals.yml entry for a TKMS company with no careers_url/api field; a URL typed with a typo that fails the resolver's validation; an entry using a field name the provider doesn't read (e.g. 'url' instead of 'api'); a company that migrated off TKMS so the host pattern no longer matches.

Understand the failure class

Background: "missing required config value" errors: why libraries refuse to start when a configuration key is empty, unset, or blank — this error's family across 48 libraries.

Related errors


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

Appendix: source

Thrown at providers/tkms.mjs:157

function resolveMaxPages(entry) {
  const v = entry?.max_pages;
  if (Number.isInteger(v) && v > 0) return Math.min(v, MAX_PAGES);
  return MAX_PAGES;
}

/** @type {Provider} */
export default {
  id: 'tkms',

  detect(entry) {
    const url = entry.api || entry.careers_url || '';
    if (typeof url !== 'string') return null;
    return resolveConfig({ api: url }) ? { url } : null;
  },

  async fetch(entry, ctx) {
    const cfg = resolveConfig(entry);
    if (!cfg) throw new Error(`tkms: cannot resolve jobs host for ${entry.name}`);

    const wait = (ms) => (ctx.sleep ? ctx.sleep(ms) : new Promise((r) => setTimeout(r, ms)));
    const maxPages = resolveMaxPages(entry);
    const jobs = [];
    const seen = new Set();

    for (let page = 0; page < maxPages; page++) {
      if (page > 0) await wait(PAGE_DELAY_MS);
      const json = await ctx.fetchJson(cfg.queryApi, {
        method: 'POST',
        redirect: 'error',
        headers: { 'content-type': 'application/json', accept: 'application/json' },
        body: JSON.stringify({ searchQuery: '', filter: {}, subclient: cfg.subclient, locale: cfg.locale, page }),
      });
      const { nextPage, rows } = parseQuery(json, cfg);
      if (rows.length === 0) break;

      let fresh = 0;

View on GitHub (pinned to aac998c7ed)