santifer/career-ops · error · Error

workable: cannot derive feed URL for ${entry.name}

Error message

workable: cannot derive feed URL for ${entry.name}

What it means

Workable auto-detects an account from a careers_url matching https://apply.workable.com/<slug>; resolveWorkableSlug returns null when the URL is absent, not https, not on apply.workable.com, or the slug fails SLUG_RE. fetch() then throws because it has no slug to build the widget/feed URL from.

Source

Thrown at providers/workable.mjs:157

  if (!slug || !SLUG_RE.test(slug)) return null;
  return slug;
}

const widgetUrlFor = (slug) => `https://apply.workable.com/api/v1/widget/accounts/${slug}?details=true`;
const feedUrlFor = (slug) => `https://apply.workable.com/${slug}/jobs.md`;

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

  detect(entry) {
    const slug = resolveWorkableSlug(entry);
    return slug ? { url: widgetUrlFor(slug) } : null;
  },

  async fetch(entry, ctx) {
    const slug = resolveWorkableSlug(entry);
    if (!slug) throw new Error(`workable: cannot derive feed URL for ${entry.name}`);

    const referer = `https://apply.workable.com/${slug}/`;

    return serialized(async () => {
      // Primary: widget API. assertWorkableUrl + redirect:'error' together
      // guarantee the final hostname stays in the allowlist (no SSRF via
      // redirect). Retries transient failures; gives up early on a
      // long-lived Retry-After so a Cloudflare-level block on this path
      // falls through to the markdown feed instead of stalling the scan.
      const apiUrl = assertWorkableUrl(widgetUrlFor(slug));
      let payload = null;
      try {
        payload = await fetchWithRetry(ctx, () => ctx.fetchJson(apiUrl, {
          redirect: 'error',
          headers: { ...WORKABLE_HEADERS, referer },
        }));
      } catch {
        payload = null; // fall through to the markdown feed

View on GitHub (pinned to 9b17a8ac97)

Solutions

  1. Set careers_url: https://apply.workable.com/<slug> on the entry (find the slug in the company's Workable URL).
  2. If the company uses a branded domain, do not set provider: workable — let a different detection path handle it or drop the entry.
  3. Confirm the slug is alphanumeric / dash / underscore with no leading punctuation.

Example fix

# before
- name: Acme
  provider: workable
# after
- name: Acme
  provider: workable
  careers_url: https://apply.workable.com/acme
Defensive patterns

Strategy: validation

Validate before calling

import { resolveWorkableSlug } from "./providers/workable.mjs";
// before scan
if (entry.provider === "workable") {
  const slug = resolveWorkableSlug(entry);
  if (!slug) { console.warn(`skip ${entry.name}: no workable slug in careers_url`); continue; }
}

Type guard

const hasWorkableSlug = (e) => typeof resolveWorkableSlug(e) === "string";

Prevention

When it happens

Trigger: A tracked_companies/job_boards entry with provider: workable whose careers_url is missing, is a branded careers domain (e.g. https://www.acme.com/careers), uses http, or whose slug contains characters outside [A-Za-z0-9_-] (leading dot/slash, spaces).

Common situations: User sets provider: workable on a company whose careers page is a custom domain; the entry relies on detection but has no careers_url; the slug has leading punctuation.

Related errors


AI-assisted analysis of santifer/career-ops@9b17a8ac97 (2026-08-13). Data as JSON: /api/errors/90e7561b122ebf44. Report an issue: GitHub.