santifer/career-ops · error · Error

wttj: `filters` is too long

Error message

wttj: `filters` is too long (${filters.length} > ${FILTERS_MAX_LEN} chars)

What it means

resolveConfig reads the portals.yml entry's optional wttj block and rejects a `filters` string longer than FILTERS_MAX_LEN characters before it is ever sent to Algolia. This is an upfront config sanity check so an over-long filter expression is caught at configuration time rather than as an HTTP 413/400 from the Algolia API. The message reports the actual and maximum lengths.

Solutions

  1. Shorten the wttj.filters expression in portals.yml below FILTERS_MAX_LEN (check the constant's value in providers/wttj.mjs)
  2. Split the entry into multiple portals.yml entries, each with a narrower filters expression
  3. Move some narrowing into wttj.queries (search terms) instead of server-side filters
  4. Generate the filters programmatically and deduplicate clauses before writing the config

Example fix

# before
wttj:
  filters: "(company_slug=a OR company_slug=b OR ... 200 more clauses)"
# after: split across entries
wttj:
  filters: "(company_slug=a OR company_slug=b OR company_slug=c)"
  queries: ["engineer"]
Defensive patterns

Strategy: validation

Validate before calling

// validate the portals.yml entry before invoking the provider
const filters = (entry?.wttj?.filters ?? '').trim();
if (filters.length > 2000) { // match FILTERS_MAX_LEN
  throw new Error(`wttj.filters too long (${filters.length} chars) — split the entry`);
}

Type guard

function hasValidWttjFilters(entry, max = 2000) {
  const f = entry?.wttj?.filters;
  return f === undefined || (typeof f === 'string' && f.trim().length <= max);
}

Try / catch

try {
  resolveConfig(entry);
} catch (e) {
  if (e.message.startsWith('wttj: `filters` is too long')) {
    console.warn('Split wttj.filters across multiple portals.yml entries.');
  } else throw e;
}

Prevention

When it happens

Trigger: A portals.yml entry sets wttj.filters to a string whose trimmed length exceeds FILTERS_MAX_LEN — typically a very long hand-written filter expression or one concatenating many company/job filters.

Common situations: Config authors paste an enormous filter built for another board; generated filter lists accumulate too many clauses; a copy-paste duplicates the expression and blows past the cap.

Understand the failure class

Background: "Invalid value" and "allowed values are" config errors: what your library rejected and how to fix it — this error's family across 41 libraries.

Related errors


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

Appendix: source

Thrown at providers/wttj.mjs:170

  if (min || max) {
    job.salary = {
      min: min || max,
      max: max || min,
      currency: typeof h.salary_currency === 'string' ? h.salary_currency.trim().toUpperCase() : '',
    };
  }
  return job;
}

/** Resolve config: queries and/or an Algolia filter expression, + per-query hit cap. */
function resolveConfig(entry) {
  const cfg = entry?.wttj && typeof entry.wttj === 'object' ? entry.wttj : {};
  const queries = Array.isArray(cfg.queries)
    ? cfg.queries.filter((q) => typeof q === 'string' && q.trim()).map((q) => q.trim())
    : [];
  const filters = typeof cfg.filters === 'string' && cfg.filters.trim() ? cfg.filters.trim() : '';
  if (filters.length > FILTERS_MAX_LEN) {
    throw new Error(`wttj: \`filters\` is too long (${filters.length} > ${FILTERS_MAX_LEN} chars)`);
  }
  if (queries.length === 0 && !filters) {
    throw new Error(
      'wttj: the WTTJ board is global — narrow it with `wttj: { filters: "…" }` and/or `wttj: { queries: ["…"] }`',
    );
  }
  // A filter expression already narrows the board server-side, so the empty query
  // ("match everything that passes the filter") is the useful default. Without a
  // filter there is nothing to narrow the board, so a query list stays mandatory.
  const effectiveQueries = queries.length > 0 ? queries : [''];
  const cap = filters ? FILTERED_MAX_HITS_CAP : MAX_HITS_CAP;
  const maxHits =
    Number.isInteger(cfg.max_hits) && cfg.max_hits > 0 ? Math.min(cfg.max_hits, cap) : DEFAULT_MAX_HITS;
  return { queries: effectiveQueries, filters, maxHits };
}

/** @type {Provider} */
export default {

View on GitHub (pinned to aac998c7ed)