santifer/career-ops · error · Error
join: cannot extract slug from careers_url
Error message
join: cannot extract slug from careers_url
What it means
Thrown by join.fetch() when extractSlug(entry.careers_url) returns null. extractSlug requires hostname to be exactly 'join.com' and the pathname to match /companies/<slug>; anything else yields null. Since detect() also gates on extractSlug, hitting this in fetch() means the entry passed detect by a different path or the careers_url was mutated between detect and fetch.
Source
Thrown at providers/join.mjs:51
export function extractNextData(html) {
if (typeof html !== 'string') return null;
const match = html.match(/<script[^>]+__NEXT_DATA__[^>]*>([\s\S]*?)<\/script>/);
if (!match) return null;
try { return JSON.parse(match[1]); } catch { return null; }
}
/** @type {Provider} */
export default {
id: 'join',
detect(entry) {
return extractSlug(entry.careers_url) ? { url: entry.careers_url } : null;
},
async fetch(entry, ctx) {
const slug = extractSlug(entry.careers_url);
if (!slug) throw new Error('join: cannot extract slug from careers_url');
const baseUrl = `https://join.com/companies/${slug}`;
const allItems = [];
// redirect:'error' prevents SSRF via server-side redirects; baseUrl is
// always reconstructed as https://join.com/... so the host is pinned
// regardless of the original careers_url.
const firstHtml = await ctx.fetchText(baseUrl, { redirect: 'error' });
const firstData = extractNextData(firstHtml);
const state = firstData?.props?.pageProps?.initialState;
if (!state) throw new Error('join: __NEXT_DATA__ not found or unexpected structure');
const firstJobs = state.jobs?.items;
if (!Array.isArray(firstJobs)) throw new Error('join: __NEXT_DATA__ not found or unexpected structure');
allItems.push(...firstJobs);
// Honor a context page cap — verify-portals' liveness probe sets
// `ctx.maxPages: 1` so it only needs to know a board is live, not itsView on GitHub (pinned to 9b17a8ac97)
Solutions
- Set careers_url to the canonical https://join.com/companies/<slug> form.
- If the company uses a custom domain that proxies join.com, find the underlying join.com/companies/<slug> URL and use it.
- Verify hostname is exactly 'join.com' (no subdomain) and the path starts with /companies/.
Example fix
# before acme: provider: join careers_url: https://www.join.com/companies/acme # after — exact host, canonical path acme: provider: join careers_url: https://join.com/companies/acme
Defensive patterns
Strategy: type-guard
Validate before calling
import { extractSlug } from './providers/join.mjs';
// detect() already calls extractSlug; mirror it in config validation:
for (const e of joinEntries) {
if (!extractSlug(e.careers_url)) console.warn(`join entry ${e.name}: careers_url must be https://join.com/companies/<slug>`);
} Type guard
/** @param {string} url @returns {boolean} */
function isJoinCompanyUrl(url) {
try {
const p = new URL(url);
return p.hostname.toLowerCase() === 'join.com' && /^\/companies\/[^/?#]+/.test(p.pathname);
} catch {
return false;
}
} Prevention
- Only assign provider: join to entries whose careers_url matches https://join.com/companies/<slug>.
- Lint join entries for the exact host (no www) and the /companies/ path prefix.
- Run provider.detect() in a dry-run config check to catch mismatches before a real scan.
When it happens
Trigger: A careers_url whose host is not exactly join.com (e.g. www.join.com, a CNAME, or a typo like join.companies); a pathname that doesn't start with /companies/; an empty or non-string careers_url; or a URL that fails new URL() parsing.
Common situations: Entry copied from another provider with the wrong URL; the company changed its careers URL to a custom domain that proxies join.com; a trailing slash or query string that shifts the pathname match.
Related errors
- arbeitnow: invalid URL: ${url}
- ashby: invalid URL: ${url}
- bamboohr: invalid URL: ${url}
- breezy: invalid URL: ${url}
- flowxtra: untrusted hostname "${parsed.hostname}" — must be
AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13).
Data as JSON: /api/errors/660ab03a53fad9ff.
Report an issue: GitHub.