{"record":{"id":"43090e83788a848a","repo":"jackwener/OpenCLI","slug":"malformed-chatgpt-deep-research-label-missing","errorCode":null,"errorMessage":"Malformed ChatGPT Deep Research ${label}: missing source URL.","messagePattern":"Malformed ChatGPT Deep Research (.+?): missing source URL\\.","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/chatgpt/utils.js","lineNumber":1354,"sourceCode":"\nfunction extractDeepResearchSourcesFromReportMessage(reportMessage) {\n    const metadata = reportMessage?.metadata && typeof reportMessage.metadata === 'object'\n        ? reportMessage.metadata\n        : {};\n    const references = Array.isArray(metadata.content_references) ? metadata.content_references : [];\n    const safeUrls = Array.isArray(metadata.safe_urls) ? metadata.safe_urls : [];\n    const groups = Array.isArray(metadata.search_result_groups) ? metadata.search_result_groups : [];\n    const byUrl = new Map();\n\n    const addSource = (source = {}, label = 'source') => {\n        if (!source || typeof source !== 'object') {\n            throw new CommandExecutionError(`Malformed ChatGPT Deep Research ${label}: expected object source row.`);\n        }\n        const rawUrl = String(source.url || source.href || source.safe_url || '').trim();\n        const title = String(source.title || source.name || source.text || '').trim();\n        if (!rawUrl) {\n            if (title || source.matched_text || source.metadata) {\n                throw new CommandExecutionError(`Malformed ChatGPT Deep Research ${label}: missing source URL.`);\n            }\n            return;\n        }\n        if (!/^https?:\\/\\//i.test(rawUrl)) {\n            throw new CommandExecutionError(`Malformed ChatGPT Deep Research ${label}: invalid source URL.`);\n        }\n        if (!byUrl.has(rawUrl)) {\n            byUrl.set(rawUrl, { title, url: rawUrl });\n        } else if (title && !byUrl.get(rawUrl).title) {\n            byUrl.get(rawUrl).title = title;\n        }\n    };\n\n    for (const reference of references) {\n        const hasDirectSource = reference && typeof reference === 'object'\n            && (reference.url || reference.href || reference.safe_url || reference.title || reference.name || reference.text || reference.matched_text);\n        if (hasDirectSource) addSource(reference, 'content reference');\n        if (reference?.matched_text) addSource({ title: reference.matched_text, url: reference.url }, 'matched content reference');","sourceCodeStart":1336,"sourceCodeEnd":1372,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/chatgpt/utils.js#L1336-L1372","documentation":"addSource extracted a source row that has no usable URL: source.url, source.href, and source.safe_url are all empty. If the row carries other identifying data (title, matched_text, metadata) the library refuses to emit a URL-less citation and throws, because a source without a link is unusable downstream.","triggerScenarios":"A source row object exists (passes the object check) but rawUrl trims to '' — all of source.url, source.href, source.safe_url are missing/empty — while at least one of title, matched_text, or metadata is present.","commonSituations":"Search-result groups whose entries only have matched_text snippets; backend sending annotation rows with title but stripped/omitted URL fields (e.g. after redaction or a new field name); tests feeding hand-built fixture objects missing url keys.","solutions":["Log the offending row and add the missing key mapping (e.g. source.link or source.target_href) to the URL extraction chain.","Pre-filter rows: only pass rows where (s.url||s.href||s.safe_url) is a non-empty string.","Normalize upstream data so every source row carries a URL before calling the extractor.","Catch the error and skip that source row, degrading to a citation-less result."],"exampleFix":"// before\ncollectSources(row, addSource);\n// after\nconst hasUrl = row && typeof row === 'object' && String(row.url || row.href || row.safe_url || '').trim();\nif (hasUrl) collectSources(row, addSource);","handlingStrategy":"validation","validationCode":"rows.forEach(r => {\n  const u = String(r?.url || r?.href || r?.safe_url || '').trim();\n  if (!u && (r?.title || r?.matched_text || r?.metadata)) console.warn('source row missing URL', r);\n});","typeGuard":"const hasSourceUrl = (s) => !!s && typeof s === 'object' && String(s.url || s.href || s.safe_url || '').trim().length > 0;","tryCatchPattern":"try {\n  extractDeepResearchSources(metadata);\n} catch (err) {\n  if (String(err.message).includes('missing source URL')) {\n    console.warn('skipping citation without URL:', err.message);\n    return { sources: [], degraded: true };\n  }\n  throw err;\n}","preventionTips":["Map alternate URL field names (link, target_href) into url before extraction","Drop rows lacking any URL key instead of passing them through","Assert in tests that every fixture source row has a truthy url","Watch for backend redaction changes that strip URLs from annotations"],"tags":["validation","deep-research","missing-field"],"backgroundTag":"missing-required-field","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}