{"record":{"id":"b068a1eedbe9cef6","repo":"jackwener/OpenCLI","slug":"upwork-search-state-had-an-unexpected-jobs-shape","errorCode":null,"errorMessage":"Upwork search state had an unexpected jobs shape; expected window.__NUXT__.state.jobsSearch.jobs to be an array.","messagePattern":"Upwork search state had an unexpected jobs shape; expected window\\.__NUXT__\\.state\\.jobsSearch\\.jobs to be an array\\.","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/upwork/search.js","lineNumber":100,"sourceCode":"        }\n        catch (e) {\n            throw new CommandExecutionError(`Failed to read Upwork search state: ${e?.message ?? e}`, 'The Nuxt state global was not reachable; try again after opening Upwork in the connected browser.');\n        }\n\n        if (payload?.onLogin) {\n            throw new AuthRequiredError('upwork.com', 'Upwork redirected to login. Open https://www.upwork.com in the connected browser and sign in, then retry.');\n        }\n        if (payload?.challenge) {\n            throw new CommandExecutionError('Upwork served a Cloudflare challenge page', 'Open https://www.upwork.com in the connected browser and clear the challenge, then retry.');\n        }\n        if (!payload?.ready) {\n            throw new CommandExecutionError('Upwork search state (window.__NUXT__.state.jobsSearch) was not present within 15s', 'The page may not have finished hydrating, or the SSR state shape may have changed.');\n        }\n        if (!isPlainObject(payload)) {\n            throw new CommandExecutionError('Upwork search returned an unexpected Browser Bridge payload shape');\n        }\n        if (!payload.jobsPresent || !Array.isArray(payload.jobs)) {\n            throw new CommandExecutionError('Upwork search state had an unexpected jobs shape; expected window.__NUXT__.state.jobsSearch.jobs to be an array.');\n        }\n\n        const jobs = payload.jobs;\n        if (jobs.length === 0) {\n            throw new EmptyResultError('upwork search', `No Upwork jobs matched \"${query}\"${location ? ` in ${location}` : ''}`);\n        }\n\n        const offset = (pageNum - 1) * perPage;\n        const rows = jobsToListRows(jobs, { offset, limit: perPage });\n        if (rows.length === 0) {\n            throw new CommandExecutionError('Upwork search results did not include any job with a valid ciphertext id; cannot produce round-trippable detail rows.');\n        }\n        return rows;\n    },\n});\n","sourceCodeStart":82,"sourceCodeEnd":116,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/upwork/search.js#L82-L116","documentation":"A CommandExecutionError thrown when payload.jobsPresent is falsy or payload.jobs is not an array - i.e. the Nuxt state exists but its jobsSearch.jobs field is missing or of the wrong type. The library throws instead of returning garbage rows because every result row must carry a round-trippable job id.","triggerScenarios":"Running upwork search when Upwork's SSR state shape changed (jobsSearch exists but jobs renamed/removed), or the page reached a state where jobsSearch is present but holds no jobs array (e.g. an error variant of the search page).","commonSituations":"An Upwork frontend rollout altering the __NUXT__.state.jobsSearch schema; querying a search URL variant that produces a different state shape; a partially hydrated page where jobsSearch exists but jobs is undefined.","solutions":["Inspect window.__NUXT__.state.jobsSearch in the connected browser's DevTools to see the actual shape.","Retry after a full page load in case hydration was partial.","If Upwork permanently changed the schema, update the CLI's state extraction to the new key path.","Ensure you are hitting the standard Upwork jobs search URL rather than a special page variant."],"exampleFix":"// before\nconst jobs = state.jobsSearch.jobs // undefined after Upwork renamed the field\n// after\nconst jobs = state.jobsSearch.jobs || state.jobsSearch.searchResults?.jobs; // adapt to new schema, and throw the explicit shape error if still absent","handlingStrategy":"type-guard","validationCode":"// verify the state shape before trusting results\nconst shapeOk = await bridge.eval(() => {\n  const js = window.__NUXT__ && window.__NUXT__.state && window.__NUXT__.state.jobsSearch;\n  return !!js && Array.isArray(js.jobs);\n});\nif (!shapeOk) throw new Error('Upwork state shape changed; update extraction');","typeGuard":"function hasJobsArray(state) {\n  const js = state && state.jobsSearch;\n  return !!js && Array.isArray(js.jobs);\n}","tryCatchPattern":"try {\n  rows = await cli.search(query);\n} catch (e) {\n  if (String(e.message).includes('unexpected jobs shape')) {\n    await dumpNuxtStateForInspection(); // capture __NUXT__.state for schema debugging\n    throw e; // requires a code fix once Upwork's schema is known\n  } else throw e;\n}","preventionTips":["After Upwork frontend releases, spot-check window.__NUXT__.state.jobsSearch in DevTools.","Adapt extraction to fallback key paths when Upwork renames fields.","Always hit the standard jobs search URL; special page variants use different state shapes.","Retry once after a full reload in case partial hydration produced a malformed payload."],"tags":["schema-change","upwork","scraping","state-shape"],"backgroundTag":"unexpected-payload-shape","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}