santifer/career-ops · error

arbeitsagentur: entry

Error message

arbeitsagentur: entry "${entry.name || '(unnamed)'}" has no arbeitsagentur.keywords[]

What it means

The arbeitsagentur provider requires each portals.yml entry using it to define arbeitsagentur.keywords[] — the list of search terms driving the 'was' query parameter. An entry with no keywords cannot produce any requests, so fetch throws immediately naming the entry.

Solutions

  1. Add arbeitsagentur.keywords to the entry named in the message, e.g. arbeitsagentur: { keywords: ['Softwareentwickler'], umkreis: 50 }.
  2. Check YAML indentation — keywords must sit under the arbeitsagentur key of that entry, not a sibling level.
  3. Verify the key spelling is exactly `keywords` (plural, array) and that entries are non-empty strings.
  4. Run node audit-portals.mjs or verify-pipeline.mjs to catch misconfigured entries before a scan run.

Example fix

# before
- name: bundesinnenministerium
  provider: arbeitsagentur
  location: Berlin
# after
- name: bundesinnenministerium
  provider: arbeitsagentur
  arbeitsagentur:
    keywords: ["IT-Projektmanagement", "Softwareentwickler"]
    wo: Berlin
    umkreis: 50
Defensive patterns

Strategy: validation

Validate before calling

for (const entry of config.job_boards) {
  if (entry.provider === 'arbeitsagentur') {
    const kws = entry.arbeitsagentur?.keywords;
    if (!Array.isArray(kws) || kws.length === 0 || !kws.every(k => typeof k === 'string' && k.trim())) {
      throw new Error(`entry "${entry.name}" needs a non-empty arbeitsagentur.keywords[] array`);
    }
  }
}

Type guard

function hasArbeitsagenturKeywords(entry) {
  const kws = entry?.arbeitsagentur?.keywords;
  return Array.isArray(kws) && kws.length > 0 && kws.every(k => typeof k === 'string' && k.trim().length > 0);
}

Try / catch

try {
  await provider.fetch(entry, ctx);
} catch (err) {
  if (String(err.message).includes('has no arbeitsagentur.keywords')) {
    console.error(`Fix portals.yml: entry "${entry.name}" is missing arbeitsagentur.keywords[] (check indentation and plural key)`);
  }
  throw err;
}

Prevention

When it happens

Trigger: A job_boards entry with provider: arbeitsagentur lacks the arbeitsagentur block, has keywords: [] (or an empty/whitespace-only list after parseArbeitsagenturConfig filters), or misspells the key (keyword: instead of keywords:) so it parses to an empty array.

Common situations: Copy-pasting an entry from another provider (arbeitnow/agentic-jobs don't need keywords) without adding the arbeitsagentur block; a YAML indentation mistake nesting keywords under the wrong parent; renaming the key during cleanup; adding a new board entry and forgetting the required field.

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/69f1113260db9ae6. Report an issue: GitHub.

Appendix: source

Thrown at providers/arbeitsagentur.mjs:160

// candidate whose title makes no remote claim keeps its real city, which is the
// fail-closed behaviour an unverifiable lookup had in v4: the `Deutschlandweit
// (Homeoffice)` marker exempts a job from the commute location_filter, so
// tagging on nv_true alone would smuggle every hybrid past it.

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

  /**
   * Fetches and normalizes postings from the Arbeitsagentur Jobsuche API.
   * @param {{ name?: string, arbeitsagentur?: any }} entry
   * @param {{ fetchJson: (url: string, opts?: object) => Promise<any> }} ctx
   * @returns {Promise<Array<{title: string, url: string, company: string, location: string}>>}
   */
  async fetch(entry, ctx) {
    const { keywords, wo, umkreis, days, size, remoteNationwide, remoteMatch, remoteMaxPages } = parseArbeitsagenturConfig(entry);
    if (!keywords.length) {
      throw new Error(`arbeitsagentur: entry "${entry.name || '(unnamed)'}" has no arbeitsagentur.keywords[]`);
    }

    /** @param {string} was @param {Record<string,string>} [extra] */
    const fetchKeyword = async (was, extra = {}) => {
      const params = new URLSearchParams({
        was,
        size: String(size),
        page: '1',
        angebotsart: '1', // 1 = ARBEIT (employment; excludes Ausbildung/Selbständigkeit)
        veroeffentlichtseit: String(days),
        ...extra,
      });
      // redirect:'error' prevents SSRF via server-side redirects.
      const json = await ctx.fetchJson(`${API_URL}?${params.toString()}`, {
        headers: { 'X-API-Key': API_KEY, accept: 'application/json' },
        redirect: 'error',
        timeoutMs: 12_000,
      });

View on GitHub (pinned to aac998c7ed)