jackwener/OpenCLI · error · CommandExecutionError
Malformed ChatGPT Deep Research ${label}: missing source URL
Error message
Malformed ChatGPT Deep Research ${label}: missing source URL. What it means
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.
Source
Thrown at clis/chatgpt/utils.js:1354
function extractDeepResearchSourcesFromReportMessage(reportMessage) {
const metadata = reportMessage?.metadata && typeof reportMessage.metadata === 'object'
? reportMessage.metadata
: {};
const references = Array.isArray(metadata.content_references) ? metadata.content_references : [];
const safeUrls = Array.isArray(metadata.safe_urls) ? metadata.safe_urls : [];
const groups = Array.isArray(metadata.search_result_groups) ? metadata.search_result_groups : [];
const byUrl = new Map();
const addSource = (source = {}, label = 'source') => {
if (!source || typeof source !== 'object') {
throw new CommandExecutionError(`Malformed ChatGPT Deep Research ${label}: expected object source row.`);
}
const rawUrl = String(source.url || source.href || source.safe_url || '').trim();
const title = String(source.title || source.name || source.text || '').trim();
if (!rawUrl) {
if (title || source.matched_text || source.metadata) {
throw new CommandExecutionError(`Malformed ChatGPT Deep Research ${label}: missing source URL.`);
}
return;
}
if (!/^https?:\/\//i.test(rawUrl)) {
throw new CommandExecutionError(`Malformed ChatGPT Deep Research ${label}: invalid source URL.`);
}
if (!byUrl.has(rawUrl)) {
byUrl.set(rawUrl, { title, url: rawUrl });
} else if (title && !byUrl.get(rawUrl).title) {
byUrl.get(rawUrl).title = title;
}
};
for (const reference of references) {
const hasDirectSource = reference && typeof reference === 'object'
&& (reference.url || reference.href || reference.safe_url || reference.title || reference.name || reference.text || reference.matched_text);
if (hasDirectSource) addSource(reference, 'content reference');
if (reference?.matched_text) addSource({ title: reference.matched_text, url: reference.url }, 'matched content reference');View on GitHub (pinned to 49907e53dc)
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.
Example fix
// before collectSources(row, addSource); // after const hasUrl = row && typeof row === 'object' && String(row.url || row.href || row.safe_url || '').trim(); if (hasUrl) collectSources(row, addSource);
Defensive patterns
Strategy: validation
Validate before calling
rows.forEach(r => {
const u = String(r?.url || r?.href || r?.safe_url || '').trim();
if (!u && (r?.title || r?.matched_text || r?.metadata)) console.warn('source row missing URL', r);
}); Type guard
const hasSourceUrl = (s) => !!s && typeof s === 'object' && String(s.url || s.href || s.safe_url || '').trim().length > 0;
Try / catch
try {
extractDeepResearchSources(metadata);
} catch (err) {
if (String(err.message).includes('missing source URL')) {
console.warn('skipping citation without URL:', err.message);
return { sources: [], degraded: true };
}
throw err;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "Missing required field" and "field is required" errors: why libraries reject payloads that omit mandatory fields — this error's family across 20 libraries.
- Parsing and encoding errors: unexpected token, malformed input — why parsers reject input and how to find the real culprit.
Related errors
- ${label} did not include a stable ${field}.
- Bilibili comments reply ${index + 1} was missing rpid
- Bilibili comments reply ${index + 1} was missing ctime
- Malformed ChatGPT Deep Research ${label}: expected object so
- Malformed ChatGPT Deep Research ${label}: invalid source URL
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/43090e83788a848a.
Report an issue: GitHub.