{"record":{"id":"b5d7fbadfb9229af","repo":"santifer/career-ops","slug":"radancy-unexpected-fragment-response-shape-at-page-page","errorCode":null,"errorMessage":"radancy: unexpected fragment response shape at page ${page} (results is not a string)","messagePattern":"radancy: unexpected fragment response shape at page (.+?) \\(results is not a string\\)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"providers/radancy.mjs","lineNumber":398,"sourceCode":"          for (; page <= lastPage && jobs.length < maxJobs; page++) {\n            await wait(PAGE_DELAY_MS);\n            let rows;\n            try {\n              const json = await fetchJsonWithRetry(ctx, buildFragmentUrl(listUrl, page), {\n                redirect: 'error',\n                headers: { accept: 'application/json', 'x-requested-with': 'XMLHttpRequest' },\n              });\n              // A STRING `results` — even one that parses to zero rows, which\n              // is what a genuine last page looks like live (Walgreens' own\n              // past-the-end page answers hasJobs:false with a non-empty\n              // shell string that simply contains no job rows) — is the only\n              // form the documented \"no more jobs\" signal takes. A missing,\n              // null, or wrong-typed `results` has never been observed as\n              // that signal, so it's a malformed response, not an empty\n              // page: silently coercing it to [] would end the walk early\n              // exactly like a real empty page does, with no error raised.\n              if (typeof json?.results !== 'string') {\n                throw new Error(`radancy: unexpected fragment response shape at page ${page} (results is not a string)`);\n              }\n              rows = json.results ? parseResults(json.results, origin) : [];\n            } catch (err) {\n              if (probing) throw err; // propagate ProbePageBudgetReached (or any rejection) unwrapped\n              console.error(\n                `⚠️  radancy: ${entry.name} truncated at page ${page} of ${lastPage}`\n                + ` (${jobs.length} jobs): ${err.message}`,\n              );\n              stopReason = 'error';\n              break; // keep what we have; a mid-walk blip shouldn't discard earlier pages\n            }\n            // A clean, structured empty page (`rows.length === 0`, not a\n            // thrown error) is a legitimate natural stop even when it lands\n            // well short of `totalResults`/`totalPages` — no different from\n            // any other tenant undercounting. Observed live on one very\n            // large tenant landing exactly at offset 10,000 (100 pages of\n            // 100 — the default Elasticsearch/Solr `max_result_window`);\n            // unlike `workday.mjs`'s analogous, multi-tenant-confirmed","sourceCodeStart":380,"sourceCodeEnd":416,"githubUrl":"https://github.com/santifer/career-ops/blob/aac998c7ed7248ea853b720ceeb1fdbeb322fc5d/providers/radancy.mjs#L380-L416","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Re-run the scan; transient CDN/proxy error bodies are the most common cause","Check whether Radancy changed the fragment response schema and update the provider parser","Reduce maxPages/limit the walk to shallow pages to isolate which page fails","Report/inspect the raw response for that tenant board to see what is actually returned"],"exampleFix":"null","handlingStrategy":"try-catch","validationCode":"null","typeGuard":"function isWellFormedFragment(json) {\n  return json !== null && typeof json === 'object' && typeof json.results === 'string';\n}","tryCatchPattern":"try {\n  jobs = await radancy.fetch(entry, ctx);\n} catch (err) {\n  if (err.message.includes('unexpected fragment response shape')) {\n    console.warn(`Radancy fragment shape changed for ${entry.name} — retrying later`);\n    return [];\n  }\n  throw err;\n}","preventionTips":["Re-run failed scans before investigating — CDN/proxy error bodies are often transient","Keep maxPages modest so mid-walk failures are cheap to retry","Monitor Radancy board behaviour after ATS migrations; the fragment schema is the fragile part"],"tags":["network","ats","schema-validation","pagination"],"backgroundTag":"unexpected-response-shape","analyzedSha":"aac998c7ed7248ea853b720ceeb1fdbeb322fc5d","analyzedAt":"2026-09-16T06:35:29.214Z","contentChangedAt":"2026-09-16T06:35:29.214Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}