{"record":{"id":"c5edba3424b42e43","repo":"jackwener/OpenCLI","slug":"malformed-chatgpt-deep-research-label-expected","errorCode":null,"errorMessage":"Malformed ChatGPT Deep Research ${label}: expected object source row.","messagePattern":"Malformed ChatGPT Deep Research (.+?): expected object source row\\.","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/chatgpt/utils.js","lineNumber":1348,"sourceCode":"    try {\n        return JSON.parse(value);\n    } catch {\n        return null;\n    }\n}\n\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    };","sourceCodeStart":1330,"sourceCodeEnd":1366,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/chatgpt/utils.js#L1330-L1366","documentation":"Thrown by the addSource helper that normalizes Deep Research citation/source rows. Each row from metadata.safe_urls or metadata.search_result_groups must be a plain object; if a row is null, a primitive (string/number), or an array, the code cannot extract url/title fields and throws this error instead of silently emitting a corrupt source entry.","triggerScenarios":"metadata.safe_urls or metadata.search_result_groups contains a null, undefined, string, number, or array element passed to addSource (typeof source !== 'object' or source is falsy).","commonSituations":"ChatGPT backend schema changes to Deep Research annotation payloads; upstream responses mixing string URLs directly into safe_urls instead of objects; partial/JSON-decoded rows where null entries survived filtering.","solutions":["Inspect the raw metadata.search_result_groups / metadata.safe_urls array and drop non-object entries before calling the extraction helper.","Coerce string URL rows into {url} objects upstream: rows.map(r => typeof r === 'string' ? {url: r} : r).filter(Boolean).","Pin/vendor the ChatGPT client version matching the current backend payload shape, or update the parser for the new row schema.","Wrap extraction in try-catch and fall back to rendering the un-normalized sources."],"exampleFix":"// before\nmetadata.search_result_groups.forEach(group => collectSources(group, addSource));\n// after\nconst rows = (metadata.search_result_groups || []).filter(s => s && typeof s === 'object' && !Array.isArray(s));\nrows.forEach(group => collectSources(group, addSource));","handlingStrategy":"validation","validationCode":"const rows = Array.isArray(metadata?.search_result_groups) ? metadata.search_result_groups : [];\nconst bad = rows.filter(r => !r || typeof r !== 'object' || Array.isArray(r));\nif (bad.length) console.warn('dropping non-object source rows', bad);","typeGuard":"const isSourceRow = (s) => !!s && typeof s === 'object' && !Array.isArray(s);","tryCatchPattern":"try {\n  extractDeepResearchSources(metadata);\n} catch (err) {\n  if (String(err.message).includes('expected object source row')) {\n    metadata.search_result_groups = metadata.search_result_groups.filter(isSourceRow);\n    return extractDeepResearchSources(metadata);\n  }\n  throw err;\n}","preventionTips":["Filter source arrays with a non-array object guard before extraction","Keep a fixture test mirroring the live Deep Research metadata shape","Never hand-write source rows in tests without url/href keys","Log raw metadata when extraction throws to speed up schema-change diagnosis"],"tags":["validation","deep-research","payload-parsing"],"backgroundTag":"schema-validation-failed","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}