{"record":{"id":"63a5d1f4a4b2d3e5","repo":"jackwener/OpenCLI","slug":"malformed-chatgpt-deep-research-label-invalid","errorCode":null,"errorMessage":"Malformed ChatGPT Deep Research ${label}: invalid source URL.","messagePattern":"Malformed ChatGPT Deep Research (.+?): invalid source URL\\.","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/chatgpt/utils.js","lineNumber":1359,"sourceCode":"    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');\n        if (reference?.metadata) addSource(reference.metadata, 'content reference metadata');\n    }\n    for (const url of safeUrls) addSource(typeof url === 'string' ? { url } : url, 'safe URL');\n    for (const group of groups) {\n        for (const entry of [","sourceCodeStart":1341,"sourceCodeEnd":1377,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/chatgpt/utils.js#L1341-L1377","documentation":"addSource requires source URLs to be absolute http(s) URLs. After extracting rawUrl, if it does not match /^https?:\\/\\//i the row is rejected with this error. This guards against protocol-relative, relative, or non-HTTP schemes (ftp:, javascript:, internal paths) being surfaced as citations.","triggerScenarios":"A source row whose url/href/safe_url holds a relative path ('/search?q=x'), a protocol-relative URL ('//example.com'), a non-http scheme ('ftp://...', 'file://...'), or a malformed value like 'example.com'.","commonSituations":"Backend emitting relative CDN or internal links; scraped/citation data using bare domains without scheme; misconfigured proxies rewriting absolute URLs to root-relative ones.","solutions":["Normalize the URL before extraction: prefix 'https:' for protocol-relative values or resolve relative URLs against a base origin.","Pre-validate rows with /^https?:\\/\\//i and drop or repair non-conforming entries.","Fix the upstream producer to always emit absolute http(s) URLs.","Catch and skip the row, logging it as a dropped citation."],"exampleFix":"// before\naddSource({ title: 'Doc', url: '/docs/page' });\n// after\nconst u = '/docs/page';\naddSource({ title: 'Doc', url: u.startsWith('//') ? 'https:' + u : new URL(u, 'https://example.com').href });","handlingStrategy":"validation","validationCode":"const ABS_URL = /^https?:\\/\\//i;\nrows.forEach(r => {\n  const u = String(r?.url || r?.href || r?.safe_url || '').trim();\n  if (u && !ABS_URL.test(u)) console.warn('non-absolute source URL', u);\n});","typeGuard":"const isHttpUrl = (s) => typeof s === 'string' && /^https?:\\/\\//i.test(s.trim());","tryCatchPattern":"try {\n  extractDeepResearchSources(metadata);\n} catch (err) {\n  if (String(err.message).includes('invalid source URL')) {\n    console.warn('dropping source with bad URL:', err.message);\n    return { sources: [], degraded: true };\n  }\n  throw err;\n}","preventionTips":["Resolve relative URLs against a base origin before extraction","Upgrade protocol-relative ('//host') URLs to 'https:'","Reject or repair bare-domain strings by prepending 'https://'","Validate upstream citation producers emit absolute http(s) URLs only"],"tags":["validation","deep-research","url-validation"],"backgroundTag":"invalid-url-format","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}