santifer/career-ops · error · Error
wttj: the WTTJ board is global — narrow it with `wttj
Error message
wttj: the WTTJ board is global — narrow it with `wttj: { filters: "…" }` and/or `wttj: { queries: ["…"] }` What it means
resolveConfig refuses to run a WTTJ scan when the entry defines neither any non-empty wttj.queries nor a wttj.filters expression. Welcometothejungle's board is global, so scanning it unfiltered would return arbitrary worldwide postings; the guard forces the config to narrow the board either server-side (filters) or via search terms (queries). The error text itself documents the exact fix shape.
Solutions
- Add wttj.filters with an expression narrowing the board (e.g. country or jobs_profile slug)
- Add wttj.queries with one or more non-empty search terms (e.g. "senior backend")
- Verify YAML indentation: queries and filters must sit inside the entry's wttj: block, and strings must not be blank
- Use both: filters to constrain location/contract and queries for titles
Example fix
# before
career-ops:
enabled: true
wttj: {}
# after
career-ops:
enabled: true
wttj:
filters: "country_slug=france"
queries: ["backend engineer"] Defensive patterns
Strategy: validation
Validate before calling
// check the entry before enabling the wttj provider
const w = entry?.wttj ?? {};
const queries = Array.isArray(w.queries) ? w.queries.filter(q => typeof q === 'string' && q.trim()) : [];
const filters = typeof w.filters === 'string' ? w.filters.trim() : '';
if (queries.length === 0 && !filters) {
throw new Error('wttj entry needs wttj.filters and/or non-empty wttj.queries');
} Type guard
function isNarrowedWttjEntry(entry) {
const w = entry?.wttj;
if (!w || typeof w !== 'object') return false;
const qs = Array.isArray(w.queries) ? w.queries.filter(q => typeof q === 'string' && q.trim()) : [];
const f = typeof w.filters === 'string' ? w.filters.trim() : '';
return qs.length > 0 || f.length > 0;
} Try / catch
try {
await scanWttj(entry);
} catch (e) {
if (e.message.includes('the WTTJ board is global')) {
console.warn('Add wttj: { filters: "…" } or wttj: { queries: ["…"] } to the portals.yml entry.');
} else throw e;
} Prevention
- Always ship a wttj block with filters and/or queries when adding a WTTJ board to portals.yml
- Watch YAML indentation — queries nested outside wttj: silently become absent
- Never leave placeholder strings that trim to empty in queries
- Run a config preflight/doctor pass over portals.yml before scanning
When it happens
Trigger: A portals.yml entry enables the wttj provider but its wttj block is missing, empty, contains only blank strings, or has queries: [] with no filters — e.g. wttj: {} after trimming.
Common situations: Adding a WTTJ entry to portals.yml and forgetting the narrowing block; deleting all queries during a cleanup; copy-pasting a template entry with placeholder values that trim to empty; a YAML indentation mistake nesting queries outside the wttj key so they aren't picked up.
Understand the failure class
Background: "X is required", "must be set", "cannot be empty": the missing-required-config error family, from Vertex AI project/location to WeChat keys — this error's family across 18 libraries.
Related errors
- wttj: `filters` is too long
- collage: cannot derive API URL for
- jobbankca: entry " " has no jobbankca.keywords[] and no…
- jobvite: cannot derive a company id for
- oraclecloud: cannot derive API URL for
AI-assisted analysis of santifer/career-ops@aac998c7ed (2026-09-16).
Data as JSON: /api/errors/4be753549bf97178.
Report an issue: GitHub.
Appendix: source
Thrown at providers/wttj.mjs:173
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 {
id: 'wttj',
detect(entry) {View on GitHub (pinned to aac998c7ed)