{"record":{"id":"151024244e54c598","repo":"jackwener/OpenCLI","slug":"relay-unavailable","errorCode":"relay_unavailable","errorMessage":"[taxonomy=relay_unavailable] site=${site} command=detail missing required detail url","messagePattern":"\\[taxonomy=relay_unavailable\\] site=(.+?) command=detail missing required detail url","errorType":"error_code","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"clis/jianyu/shared/procurement-detail.js","lineNumber":50,"sourceCode":"      const bodyText = clean(document.body ? document.body.innerText : '');\n      const maxLength = 12000;\n      const limitedText = bodyText.length > maxLength ? bodyText.slice(0, maxLength) : bodyText;\n      const dateMatch = limitedText.match(/(20\\\\d{2})[.\\\\-/年](\\\\d{1,2})[.\\\\-/月](\\\\d{1,2})/);\n      const publishTime = dateMatch\n        ? dateMatch[1] + '-' + String(dateMatch[2]).padStart(2, '0') + '-' + String(dateMatch[3]).padStart(2, '0')\n        : '';\n      return {\n        title,\n        detailText: limitedText,\n        publishTime,\n      };\n    })()\n  `);\n}\nexport async function runProcurementDetail(page, { url, site, query = '', }) {\n    const targetUrl = cleanText(url);\n    if (!targetUrl) {\n        throw taxonomyError('relay_unavailable', {\n            site,\n            command: 'detail',\n            detail: 'missing required detail url',\n        });\n    }\n    let lastError = null;\n    for (let attempt = 1; attempt <= DETAIL_MAX_ATTEMPTS; attempt += 1) {\n        try {\n            const payload = await extractDetailPayload(page, targetUrl);\n            if (!payload || typeof payload !== 'object') {\n                throw taxonomyError('extraction_drift', {\n                    site,\n                    command: 'detail',\n                    detail: `detail extraction returned invalid payload: ${targetUrl}`,\n                });\n            }\n            const row = payload;\n            const title = cleanText(row.title);","sourceCodeStart":32,"sourceCodeEnd":68,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/jianyu/shared/procurement-detail.js#L32-L68","documentation":"runProcurementDetail requires a target URL to open a detail page. If the url argument is empty or whitespace-only after cleanText, it throws a taxonomy error with code 'relay_unavailable' and detail 'missing required detail url' — the relay/data source gave nothing to fetch.","triggerScenarios":"Calling the procurement 'detail' command with url undefined, empty string, or a value that normalizes to empty (e.g. an upstream search row missing its link field).","commonSituations":"Downstream code passing a row whose URL field was not extracted (parsing drift), shell/CLI invocations omitting the --url argument, upstream list API returning items without detail links.","solutions":["Pass a non-empty url to the detail command","Check the upstream search row actually contains a detail link before calling detail","Fix the list-page parser if the URL field is being dropped","Add a pre-call check that skips/flags rows without URLs"],"exampleFix":"// before\nawait runProcurementDetail(page, { site: 'jianyu', url: row.link });\n// after\nif (row.link && row.link.trim()) {\n  await runProcurementDetail(page, { site: 'jianyu', url: row.link });\n} else {\n  console.warn('skip row without detail url', row.id);\n}","handlingStrategy":"validation","validationCode":"if (!url || !String(url).trim()) {\n  throw new Error('refusing to call detail without a url');\n}","typeGuard":"function hasDetailUrl(row) {\n  return typeof row?.link === 'string' && row.link.trim().length > 0;\n}","tryCatchPattern":"try {\n  await runProcurementDetail(page, { site, url });\n} catch (e) {\n  if (/\\[taxonomy=relay_unavailable\\]/.test(e.message)) {\n    console.warn('skipping row: no detail url');\n    return null;\n  }\n  throw e;\n}","preventionTips":["Filter upstream search rows without links before batch detail runs","Validate CLI arguments before invoking detail","Track how often rows lack URLs — it signals list-page parser drift","Default to skipping rather than failing whole batches"],"tags":["validation","missing-argument","scraping"],"backgroundTag":"missing-required-argument","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}