{"record":{"id":"6e46c878ad5a1a64","repo":"jackwener/OpenCLI","slug":"boss-detail-page-did-not-expose-a-complete-job-pos","errorCode":null,"errorMessage":"BOSS detail page did not expose a complete job posting","messagePattern":"BOSS detail page did not expose a complete job posting","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/boss/detail.js","lineNumber":138,"sourceCode":"    await navigateTo(page, 'https://www.zhipin.com/web/geek/jobs', 2);\n    for (let navigationAttempt = 0; navigationAttempt < 2; navigationAttempt++) {\n        await navigateTo(page, url, 5);\n        for (let attempt = 0; attempt < 5; attempt++) {\n            try {\n                const domRow = await readRenderedPage(page, jobId);\n                if (domRow?.name && domRow?.description && domRow?.company) return domRow;\n            } catch { /* wait for a complete render */ }\n            if (attempt < 4) await page.wait(1);\n        }\n    }\n    // The retry loop above swallows every read error, so without this check a\n    // session that got bounced to the login wall reports \"incomplete posting\".\n    // The API path this command replaced classified that as AuthRequiredError\n    // via assertOk; keep that signal rather than losing it to the UI rewrite.\n    if (await isLoginWall(page)) {\n        throw new AuthRequiredError(BOSS_DOMAIN, 'BOSS redirected the job detail page to the login flow');\n    }\n    throw new CommandExecutionError('BOSS detail page did not expose a complete job posting');\n}\n\nasync function isLoginWall(page) {\n    try {\n        return await page.evaluate(`\n            (() => {\n                const href = window.location.href || '';\n                if (/\\\\/login|\\\\/user\\\\/login|passport/.test(href)) return true;\n                return !!document.querySelector('.sign-form, .login-card, [class*=\"login-register\"]');\n            })()\n        `) === true;\n    } catch {\n        return false;\n    }\n}\ncli({\n    site: 'boss',\n    name: 'detail',","sourceCodeStart":120,"sourceCodeEnd":156,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/boss/detail.js#L120-L156","documentation":"captureJobDetail throws CommandExecutionError('BOSS detail page did not expose a complete job posting') when two navigation attempts and five render-poll passes each fail to produce a row with name, description, and company — and the page is NOT a login wall. The library requires all three fields to consider the posting readable; incomplete DOM means BOSS changed its layout, the job is gone, or rendering never completed.","triggerScenarios":"Job deleted/expired/unpublished so the page shows an error or skeleton without job content; BOSS DOM restructure breaking the .job-primary/.job-detail selectors; page rendered too slowly even after ~5 waits ×2 navigations; invalid security-id reaching a soft-404 page.","commonSituations":"Off-market or closed job postings returning empty detail sections; regional/AB-test layouts with different class names; heavy network latency causing the render wait (page.wait(1)) to be insufficient; malformed security-id passing the regex but pointing at a nonexistent job.","solutions":["Verify the security-id against a fresh `opencli boss search` — the job may have been taken down or the id mistyped.","Open the job URL (https://www.zhipin.com/job_detail/<id>.html) in a normal browser to confirm the posting still exists and renders.","Retry later or with a faster network/longer waits; transient render timeouts produce this error.","If the page renders in a browser but the CLI fails, BOSS likely changed DOM classes — update selectors in extractRenderedDetail (clis/boss/detail.js) or upgrade the package.","Try fetching from a different network/IP if risk-control is serving degraded pages."],"exampleFix":"// before\nconst row = await detail('1aBcD2ef'); // page gone → incomplete posting\n// after\nconst results = await search({ query: 'same role' });\nconst job = results.find(r => r.name === expectedName);\nif (!job) throw new Error('job no longer listed');\nconst row = await detail(job.security_id); // fresh id","handlingStrategy":"retry","validationCode":"// validate the security-id format before calling detail\nconst jobId = securityId.trim();\nif (!/^[A-Za-z0-9_-]+$/.test(jobId)) throw new Error('invalid security-id format');","typeGuard":"function isAuthRequiredError(e) { return e && (e.name === 'AuthRequiredError' || /login flow/i.test(e.message || '')); }","tryCatchPattern":"try {\n  const row = await detail(securityId);\n} catch (e) {\n  if (isCommandExecutionError(e) && /complete job posting/.test(e.message)) {\n    await sleep(5000); // transient render delay\n    const row2 = await detail(securityId);\n    if (row2) return row2;\n    throw new Error(`job ${securityId} may be off-market — verify in browser`);\n  }\n  throw e;\n}","preventionTips":["Re-fetch security-ids from a fresh `boss search`; closed jobs produce incomplete pages.","Retry once with a delay before concluding the job is gone or the DOM changed.","Verify in a real browser whether the job page renders; if it does but the CLI fails, update selectors/package.","Check for BOSS layout/AB-test changes when failures cluster across many job ids."],"tags":["boss-zhipin","dom-scraping","incomplete-page","command-execution-error"],"backgroundTag":"page-render-incomplete","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}