santifer/career-ops · error · Error

radancy: unexpected fragment response shape at page

Error message

radancy: unexpected fragment response shape at page ${page} (results is not a string)

What it means

While paging Radancy's search-jobs fragment endpoint, the provider expects each JSON fragment response to carry `results` as a string (the HTML fragment). A missing, null, or wrong-typed `results` is treated as a malformed response and thrown, because coercing it to [] would end pagination early exactly like a genuine empty page with no error.

Solutions

  1. Re-run the scan; transient CDN/proxy error bodies are the most common cause
  2. Check whether Radancy changed the fragment response schema and update the provider parser
  3. Reduce maxPages/limit the walk to shallow pages to isolate which page fails
  4. Report/inspect the raw response for that tenant board to see what is actually returned

Example fix

null
Defensive patterns

Strategy: try-catch

Validate before calling

null

Type guard

function isWellFormedFragment(json) {
  return json !== null && typeof json === 'object' && typeof json.results === 'string';
}

Try / catch

try {
  jobs = await radancy.fetch(entry, ctx);
} catch (err) {
  if (err.message.includes('unexpected fragment response shape')) {
    console.warn(`Radancy fragment shape changed for ${entry.name} — retrying later`);
    return [];
  }
  throw err;
}

Prevention

When it happens

Trigger: Radancy returns a fragment JSON whose results field is absent, null, a non-string (object/array/number), typically on a page past 1 — e.g. an HTML error page parsed as JSON, an ATS-side schema change, or the endpoint returning a different shape for deep pages.

Common situations: Radancy changes or rate-limits the fragment endpoint mid-walk; a tenant's board is misconfigured and page N returns an error payload; network middleware (proxy/CDN) injects a JSON error body.

Related errors


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

Appendix: source

Thrown at providers/radancy.mjs:398

          for (; page <= lastPage && jobs.length < maxJobs; page++) {
            await wait(PAGE_DELAY_MS);
            let rows;
            try {
              const json = await fetchJsonWithRetry(ctx, buildFragmentUrl(listUrl, page), {
                redirect: 'error',
                headers: { accept: 'application/json', 'x-requested-with': 'XMLHttpRequest' },
              });
              // A STRING `results` — even one that parses to zero rows, which
              // is what a genuine last page looks like live (Walgreens' own
              // past-the-end page answers hasJobs:false with a non-empty
              // shell string that simply contains no job rows) — is the only
              // form the documented "no more jobs" signal takes. A missing,
              // null, or wrong-typed `results` has never been observed as
              // that signal, so it's a malformed response, not an empty
              // page: silently coercing it to [] would end the walk early
              // exactly like a real empty page does, with no error raised.
              if (typeof json?.results !== 'string') {
                throw new Error(`radancy: unexpected fragment response shape at page ${page} (results is not a string)`);
              }
              rows = json.results ? parseResults(json.results, origin) : [];
            } catch (err) {
              if (probing) throw err; // propagate ProbePageBudgetReached (or any rejection) unwrapped
              console.error(
                `⚠️  radancy: ${entry.name} truncated at page ${page} of ${lastPage}`
                + ` (${jobs.length} jobs): ${err.message}`,
              );
              stopReason = 'error';
              break; // keep what we have; a mid-walk blip shouldn't discard earlier pages
            }
            // A clean, structured empty page (`rows.length === 0`, not a
            // thrown error) is a legitimate natural stop even when it lands
            // well short of `totalResults`/`totalPages` — no different from
            // any other tenant undercounting. Observed live on one very
            // large tenant landing exactly at offset 10,000 (100 pages of
            // 100 — the default Elasticsearch/Solr `max_result_window`);
            // unlike `workday.mjs`'s analogous, multi-tenant-confirmed

View on GitHub (pinned to aac998c7ed)