{"record":{"id":"722ca2762db95c0d","repo":"jackwener/OpenCLI","slug":"label-did-not-include-a-stable-text-value-722ca2","errorCode":null,"errorMessage":"${label} did not include a stable text value.","messagePattern":"(.+?) did not include a stable text value\\.","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/guazi/utils.js","lineNumber":95,"sourceCode":"    return m[1];\n}\n\nexport function requireLimit(value, def, max) {\n    const raw = value == null || value === '' ? def : value;\n    const n = typeof raw === 'number' ? raw : Number(String(raw).trim());\n    if (!Number.isInteger(n) || n < 1 || n > max) {\n        throw new ArgumentError(`limit must be an integer between 1 and ${max}`);\n    }\n    return n;\n}\n\nexport function clean(s) {\n    return String(s == null ? '' : s).replace(/\\s+/g, ' ').trim();\n}\n\nexport function requireText(value, label) {\n    const text = clean(value);\n    if (!text) throw new CommandExecutionError(`${label} did not include a stable text value.`);\n    return text;\n}\n\nexport function requireStableId(value, label) {\n    const id = String(value ?? '').trim();\n    if (!/^\\d+$/.test(id)) throw new CommandExecutionError(`${label} did not include a stable numeric id.`);\n    return id;\n}\n\n/** Fetch a Guazi mobile page as HTML text, throwing typed errors. */\nexport async function guaziFetch(path, contextHint) {\n    let resp;\n    try {\n        resp = await fetch(`${GUAZI_M_BASE}${path}`, {\n            headers: {\n                'User-Agent': UA,\n                Referer: `${GUAZI_M_BASE}/`,\n                'Accept-Language': 'zh-CN,zh;q=0.9',","sourceCodeStart":77,"sourceCodeEnd":113,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/guazi/utils.js#L77-L113","documentation":"requireText cleans whitespace from an extracted value and throws CommandExecutionError when the result is empty, using the caller's label (e.g. 'guazi car 100200300 title'). It guards against HTML pages that return 200 but fail to contain the expected field, which would otherwise silently produce blank data.","triggerScenarios":"'guazi car <clueId>' found rows but the title field parsed to null/empty/whitespace — e.g. the detail template renamed the title element, the field moved behind JS rendering, or anti-bot returned a degraded page shell.","commonSituations":"Guazi redesign moving the <h1>/title node; scraping a page where the listing exists but title loads client-side; empty string in a scraped <td> due to a layout variant; partial page served to suspected bots.","solutions":["Open the detail page in a browser and confirm the title renders in server HTML; if not, the layout/rendering changed.","Update the parser in clis/guazi/car.js to select the new title element so requireText receives a value.","Re-run with a different car id to determine whether it is page-specific or template-wide.","If pages are bot-degraded, add proper headers/cookies or slow request rate before retrying."],"exampleFix":"// before\nconst title = doc.querySelector('.car-name')?.textContent;\n// after (selector updated to current markup)\nconst title = doc.querySelector('h1.car-title, .detail-title')?.textContent;","handlingStrategy":"try-catch","validationCode":"const text = (value ?? '').replace(/\\s+/g, ' ').trim();\nif (!text) console.warn(`No text extracted for ${label} — check page markup`);","typeGuard":"function hasText(v): v is string {\n  return typeof v === 'string' && v.replace(/\\s+/g, ' ').trim().length > 0;\n}","tryCatchPattern":"try {\n  const car = await guaziCar({ clue_id: id });\n} catch (e) {\n  if (/did not include a stable text value/.test(e.message)) {\n    console.warn(`Field missing for car ${id}; page template may have changed`);\n    return null;\n  }\n  throw e;\n}","preventionTips":["Add broad/fallback CSS selectors for key fields","Spot-check rendered HTML when parsing silently degrades","Treat missing-field errors as layout-change signals, not data noise","Keep a fixture HTML test that asserts title extraction still works"],"tags":["scraping","empty-results","html-parsing"],"backgroundTag":"scraper-selector-mismatch","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}