{"record":{"id":"c558a7c48a26a7cd","repo":"jackwener/OpenCLI","slug":"maimai-cn","errorCode":null,"errorMessage":"需要登录！请先在浏览器中访问 maimai.cn 并登录","messagePattern":"需要登录！请先在浏览器中访问 maimai\\.cn 并登录","errorType":"exception","errorClass":"Error","httpStatus":401,"severity":"error","filePath":"clis/maimai/search-talents.js","lineNumber":117,"sourceCode":"\n      const res = await fetch('https://maimai.cn/api/ent/discover/search?channel=www&data_version=3.0&version=1.0.0', {\n        method: 'POST',\n        headers: {\n          'accept': '*/*',\n          'content-type': 'text/plain;charset=UTF-8',\n          'origin': 'https://maimai.cn',\n          'referer': 'https://maimai.cn/ent/talents/discover/search_v2',\n          'x-csrf-token': csrftoken,\n        },\n        credentials: 'include',\n        body: JSON.stringify(body),\n      });\n\n      const result = await res.json();\n\n      // Check login status\n      if (res.status === 401 || res.status === 403 || result.error_code === 20002) {\n        throw new Error('需要登录！请先在浏览器中访问 maimai.cn 并登录');\n      }\n\n      if (result.code !== 200 && result.code !== 0) {\n        throw new Error(result.message || result.error || 'API 请求失败');\n      }\n\n      return result;\n    }`);\n\n    if (!data || typeof data !== 'object' || Array.isArray(data)) {\n      throw new CommandExecutionError('Maimai search returned malformed API payload');\n    }\n\n    // Extract talent list from response. Missing list fields mean the API\n    // shape drifted; only an explicit empty array is a true empty result.\n    const talentListCandidates = [\n      data.data?.list,\n      data.data?.talent_list,","sourceCodeStart":99,"sourceCodeEnd":135,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/maimai/search-talents.js#L99-L135","documentation":"Thrown inside the browser-context fetch in clis/maimai/search-talents.js:117 when the talent-search API responds 401/403 or body error_code 20002 — Maimai's 'not logged in / session invalid' code. The Chinese message tells the user to visit maimai.cn in the browser and log in first, because the search relies on the browser's authenticated cookies plus a matching csrftoken.","triggerScenarios":"Running `maimai search-talents <query>` with an anonymous or expired session cookie, a csrftoken that doesn't match the session (stale cookie after re-login), or Maimai revoking the session server-side (error_code 20002 even with HTTP 200).","commonSituations":"Maimai session expired overnight; user logged out or logged in elsewhere invalidating the session; cookies cleared; csrftoken cookie rotated so the x-csrf-token header no longer matches; enterprise talent-search access not enabled for the account.","solutions":["Open https://maimai.cn/ in the CLI's browser and log in, then re-run the search","After re-login, retry so a fresh csrftoken cookie is read via CDP","If cookies look valid but 20002 persists, verify the account has talent-search (招聘) access","Check that the csrftoken cookie exists; clear cookies and log in again if it's stale"],"exampleFix":"// before (stale csrf after re-login)\nconst csrftoken = cookies.find(c => c.name === 'csrftoken')?.value || '';\n// after: refresh session and csrf before searching\nawait page.goto('https://maimai.cn/');\nconst cookies = await page.getCookies({ url: 'https://maimai.cn' });\nconst csrftoken = cookies.find(c => c.name === 'csrftoken')?.value || '';\nif (!csrftoken) throw new Error('需要登录！请先在浏览器中访问 maimai.cn 并登录');","handlingStrategy":"try-catch","validationCode":"// before searching, verify an authenticated session + csrf token\nconst cookies = await page.getCookies({ url: 'https://maimai.cn' });\nif (!cookies.some(c => c.name === 'csrftoken')) {\n  throw new Error('需要登录：请先在浏览器中访问 maimai.cn 并登录');\n}","typeGuard":"function isMaimaiAuthFailure(e) {\n  return e instanceof Error && /需要登录|error_code.*20002/i.test(e.message);\n}","tryCatchPattern":"try {\n  await searchTalents(page, query);\n} catch (e) {\n  if (isMaimaiAuthFailure(e)) {\n    console.error('Session expired — log in at maimai.cn in the browser, then retry');\n    return { needsLogin: true };\n  }\n  throw e;\n}","preventionTips":["Log in at maimai.cn before running search commands and keep the session alive","Re-read the csrftoken cookie after every re-login","Abort batch searches immediately on the first 401/403/20002 instead of continuing","Confirm the account has enterprise talent-search permissions","Schedule runs soon after a manual login to minimize expiry risk"],"tags":["authentication","login-required","csrf","session-expired"],"backgroundTag":"session-expired-login-required","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}