jackwener/OpenCLI · error · CommandExecutionError

ChatGPT conversation payload id mismatch: expected ${expecte

Error message

ChatGPT conversation payload id mismatch: expected ${expectedConversationId}, got ${payloadConversationId}.

What it means

When the caller passes expectedConversationId, the payload's own id (from conversation_id, conversationId, or id) must match it exactly. A mismatch means the response body belongs to a different conversation than the one requested, which would leak or attach Deep Research progress to the wrong thread, so the library throws.

Source

Thrown at clis/chatgpt/utils.js:1535

            report,
            html: '',
            method: source,
            sources: extractDeepResearchSourcesFromReportMessage(reportMessage),
            widgetStatus: String(widgetStateObject.status || ''),
            reportMessageId: String(reportMessage?.id || ''),
            reportLength: report.length,
        };
    }
    return buildDeepResearchProgressResult(widgetStateObject, pickFirstObject(responseMetadata), source);
}

function extractDeepResearchFromConversationPayload(payload, { expectedConversationId = '' } = {}) {
    if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
        throw new CommandExecutionError('Malformed ChatGPT conversation payload for Deep Research extraction.');
    }
    const payloadConversationId = String(payload.conversation_id || payload.conversationId || payload.id || '').trim();
    if (expectedConversationId && payloadConversationId && payloadConversationId !== expectedConversationId) {
        throw new CommandExecutionError(
            `ChatGPT conversation payload id mismatch: expected ${expectedConversationId}, got ${payloadConversationId}.`,
        );
    }
    const mapping = payload?.mapping && typeof payload.mapping === 'object' ? payload.mapping : {};
    if (!payload.mapping || typeof payload.mapping !== 'object' || Array.isArray(payload.mapping)) {
        throw new CommandExecutionError('Malformed ChatGPT conversation payload for Deep Research extraction: missing mapping.');
    }
    const candidates = [];
    for (const [messageId, node] of Object.entries(mapping)) {
        const message = node?.message || {};
        const metadata = message?.metadata || {};
        const sdk = metadata?.chatgpt_sdk || {};
        const responseMetadata = pickFirstObject(
            sdk?.response_metadata,
            sdk?.responseMetadata,
            metadata?.response_metadata,
            metadata?.responseMetadata,
        );

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Verify the request URL/conversation id matches the one used to fetch the payload (fix off-by-one id selection in the caller).
  2. Bypass cache or include the conversation id in the cache key so stale payloads are never returned.
  3. Log both ids on mismatch and re-fetch the conversation before extraction.
  4. If the mismatch is expected (e.g. server-assigned ids), only pass expectedConversationId when strict verification is required.

Example fix

// before
const data = cache.get(anyConversationId) || await fetchConversation(id);
return extractDeepResearchFromConversationPayload(data, { expectedConversationId: id });
// after
const data = await fetchConversation(id); // cache key: `conv:${id}`
return extractDeepResearchFromConversationPayload(data, { expectedConversationId: id });
Defensive patterns

Strategy: validation

Validate before calling

const payloadId = String(payload?.conversation_id || payload?.conversationId || payload?.id || '').trim();
if (expectedId && payloadId && payloadId !== expectedId) {
  throw new Error(`stale/mismatched payload: ${payloadId} != ${expectedId}`);
}

Type guard

const matchesConversation = (p, id) =>
  !!p && typeof p === 'object' && String(p.conversation_id || p.conversationId || p.id || '').trim() === id;

Try / catch

try {
  return extractDeepResearchFromConversationPayload(payload, { expectedConversationId: id });
} catch (err) {
  if (String(err.message).includes('id mismatch')) {
    const fresh = await fetchConversation(id); // bypass cache and retry once
    return extractDeepResearchFromConversationPayload(fresh, { expectedConversationId: id });
  }
  throw err;
}

Prevention

When it happens

Trigger: extractDeepResearchFromConversationPayload(payload, {expectedConversationId: 'X'}) receives a payload whose trimmed conversation_id/conversationId/id is a different non-empty string 'Y'.

Common situations: Caching layer returning a previous conversation's payload for a new id; backend reusing/redirecting to another conversation; client comparing against the wrong id field (e.g. message id vs conversation id); race where the conversation id changed after regeneration.

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/61051735cc02637f. Report an issue: GitHub.