{"record":{"id":"dbc5f0f311674b5f","repo":"jackwener/OpenCLI","slug":"douyin-search-unexpected-evaluator-payload-shape","errorCode":null,"errorMessage":"Douyin search: unexpected evaluator payload shape","messagePattern":"Douyin search: unexpected evaluator payload shape","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/douyin/search.js","lineNumber":279,"sourceCode":"        { name: 'query', required: true, positional: true, help: '搜索关键词' },\n        { name: 'limit', type: 'int', default: 10, help: `结果数量 (1-${MAX_SEARCH_LIMIT})` },\n    ],\n    columns: ['rank', 'desc', 'author', 'url', 'plays', 'likes', 'comments', 'shares'],\n    func: async (page, kwargs) => {\n        const limit = parseSearchLimit(kwargs.limit);\n        const keyword = String(kwargs.query ?? '').trim();\n        if (!keyword) {\n            throw new ArgumentError('douyin search 需要 <query> 关键词');\n        }\n        await page.goto(`https://www.douyin.com/search/${encodeURIComponent(keyword)}?type=video`);\n        let result;\n        try {\n            result = unwrapEvaluateResult(await page.evaluate(WAIT_AND_EXTRACT_JS(RENDER_TIMEOUT_MS)));\n        } catch (error) {\n            throw new CommandExecutionError(`Douyin search extraction failed: ${error instanceof Error ? error.message : String(error)}`);\n        }\n        if (!result || typeof result !== 'object') {\n            throw new CommandExecutionError('Douyin search: unexpected evaluator payload shape');\n        }\n        if (result.state === 'login_wall') {\n            throw new AuthRequiredError(\n                'www.douyin.com',\n                'Douyin search results are blocked behind a login wall — log in at https://www.douyin.com in Chrome first.',\n            );\n        }\n        if (result.state === 'empty') {\n            throw new EmptyResultError('douyin search', `No Douyin videos matched \"${keyword}\".`);\n        }\n        if (result.state === 'timeout') {\n            throw new CommandExecutionError('Douyin search did not render result cards within the timeout. Open the same search in Chrome and verify login/security state before retrying.');\n        }\n        if (!Array.isArray(result.cards)) {\n            throw new CommandExecutionError('Douyin search: evaluator returned malformed cards payload');\n        }\n        if (result.cards.length === 0) {\n            throw new EmptyResultError('douyin search', `No Douyin videos matched \"${keyword}\".`);","sourceCodeStart":261,"sourceCodeEnd":297,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/douyin/search.js#L261-L297","documentation":"The `douyin search` CLI extracts results by running WAIT_AND_EXTRACT_JS in the page and unwrapping the evaluator result. This CommandExecutionError fires when unwrapEvaluateResult returns a falsy value or a non-object, meaning the in-page evaluator never produced its expected {state, cards} payload. It signals a bridge/evaluation-layer failure rather than a Douyin page-state problem.","triggerScenarios":"page.evaluate() resolves to null/undefined (e.g. the evaluator script returned nothing, the value failed to serialize across the CDP/bridge boundary), or unwrapEvaluateResult strips a wrapper and yields a primitive like a string or number instead of the expected state object.","commonSituations":"Browser bridge/extension version mismatch after an opencli upgrade; the page navigated away mid-evaluate; browser context hung and returned undefined; serialization of the payload failed because of exotic values returned by the in-page script.","solutions":["Re-run the command — transient bridge/hydration failures often resolve on a second attempt.","Verify the bound Chrome instance and opencli browser bridge are the versions the CLI expects (restart Chrome with the bridge extension reloaded).","Confirm the page is www.douyin.com/search/... and did not navigate or crash during the 15s render window.","Check unwrapEvaluateResult against the current WAIT_AND_EXTRACT_JS return shape; update the adapter if the evaluator contract changed."],"exampleFix":"// adapter-side hardening\n// before\nconst result = unwrapEvaluateResult(await page.evaluate(WAIT_AND_EXTRACT_JS(RENDER_TIMEOUT_MS)));\n// after\nconst raw = await page.evaluate(WAIT_AND_EXTRACT_JS(RENDER_TIMEOUT_MS));\nconst result = unwrapEvaluateResult(raw) ?? null;\nif (!result || typeof result !== 'object' || typeof result.state !== 'string') {\n    throw new CommandExecutionError(`Douyin search: bad evaluator payload: ${JSON.stringify(raw)?.slice(0, 200)}`);\n}","handlingStrategy":"type-guard","validationCode":"const raw = await page.evaluate(WAIT_AND_EXTRACT_JS(RENDER_TIMEOUT_MS));\nif (raw == null) throw new Error('evaluator returned nothing');","typeGuard":"function isEvaluatorPayload(v) {\n  return v != null && typeof v === 'object' && !Array.isArray(v) && typeof v.state === 'string';\n}","tryCatchPattern":"try {\n  const result = unwrapEvaluateResult(await page.evaluate(WAIT_AND_EXTRACT_JS(RENDER_TIMEOUT_MS)));\n  if (!isEvaluatorPayload(result)) throw new CommandExecutionError('Douyin search: unexpected evaluator payload shape');\n} catch (e) {\n  if (e instanceof CommandExecutionError) { logPayloadDebug(); /* retry once */ }\n  else throw e;\n}","preventionTips":["Keep the opencli browser bridge/extension and CLI on the same version.","Assert the evaluator payload shape in a unit test against a recorded fixture.","Log the raw evaluate() result when unwrapping fails to speed diagnosis."],"tags":["browser-automation","payload-shape","douyin","page-evaluate"],"backgroundTag":"evaluator-payload-shape-mismatch","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}