{"record":{"id":"a213db202c9120e9","repo":"musistudio/claude-code-router","slug":"no-bot-gateway-conversationref-is-available-for-ca","errorCode":null,"errorMessage":"No Bot Gateway conversationRef is available for card response.","messagePattern":"No Bot Gateway conversationRef is available for card response\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/core/src/agents/codex/cli-middleware-runtime.ts","lineNumber":5527,"sourceCode":"    if (!text || !String(text).trim()) return;\n    await this.ensureStarted();\n    text = localizeBotReply(String(text), botLanguageForEvent(this.config.language, event));\n    const conversationRef = conversationRefFromEvent(event) || this.config.conversationRef;\n    if (!conversationRef) {\n      throw new Error(\"No Bot Gateway conversationRef is available for inbound bot response.\");\n    }\n    const chunks = splitBotMessage(String(text), this.config.messageChunkChars);\n    for (let index = 0; index < chunks.length; index += 1) {\n      const chunk = chunks[index];\n      const outbound = this.outboundForEvent(event, conversationRef, botTextIntent(chunk), key + \":part:\" + (index + 1));\n      await this.sendDurable(outbound, { kind: \"reply\", sourceKey: key });\n    }\n  }\n\n  async sendCardToEvent(event, card, fallbackText, key) {\n    await this.ensureStarted();\n    const conversationRef = conversationRefFromEvent(event) || this.config.conversationRef;\n    if (!conversationRef) throw new Error(\"No Bot Gateway conversationRef is available for card response.\");\n    const language = botLanguageForEvent(this.config.language, event);\n    const localizedFallback = localizeBotReply(fallbackText, language);\n    const localizedCard = language === \"zh-CN\" ? localizeBotCard(card) : card;\n    const outbound = this.outboundForEvent(event, conversationRef, { type: \"card\", card: localizedCard, fallbackText: localizedFallback }, key);\n    await this.sendDurable(outbound, { kind: \"card\", sourceKey: key });\n  }\n\n  async sendMediaToEvent(event, media, caption, key) {\n    if (!this.config.mediaEnabled) return;\n    await this.ensureStarted();\n    const conversationRef = conversationRefFromEvent(event) || this.config.conversationRef;\n    if (!conversationRef) throw new Error(\"No Bot Gateway conversationRef is available for media response.\");\n    const fallbackText = caption || media.filename || media.url || \"Attachment\";\n    const outbound = this.outboundForEvent(event, conversationRef, { type: \"media\", media, caption, fallbackText }, key);\n    await this.sendDurable(outbound, { kind: \"media\", sourceKey: key });\n  }\n\n  async sendStreamToEvent(event, streamId, text, final, key) {","sourceCodeStart":5509,"sourceCodeEnd":5545,"githubUrl":"https://github.com/musistudio/claude-code-router/blob/99f24806c6a2c660b16e53e95211c517448a6c90/packages/core/src/agents/codex/cli-middleware-runtime.ts#L5509-L5545","documentation":"sendCardToEvent() sends a rich card as a bot reply and, like sendReplyToEvent, resolves the destination from the event's conversation reference with the configured conversationRef as fallback. When neither is present it throws before building the outbound message. Cards additionally get zh-CN localization before sending.","triggerScenarios":"Sending a card response to an inbound event lacking any conversation reference while no config.conversationRef fallback is configured — e.g. truncated webhook payloads or manually constructed event objects.","commonSituations":"Same class as text replies: gateway/forwarder stripping conversation fields, schema drift after a Bot Gateway upgrade, test fixtures missing conversationRef, missing gateway config section.","solutions":["Configure conversationRef in the bot gateway settings as fallback","Fix the event source so conversation fields survive forwarding to the middleware","Send localized fallbackText via sendReplyToEvent as a degraded path when no ref can be resolved for cards","Validate inbound events contain a conversation reference before enabling card responses"],"exampleFix":"// before\nawait gateway.sendCardToEvent(event, card, fallbackText, key); // throws: no ref\n\n// after\nconst ref = conversationRefFromEvent(event) || gateway.config.conversationRef;\nif (ref) {\n  await gateway.sendCardToEvent(event, card, fallbackText, key);\n} else {\n  await gateway.sendReplyToEvent(event, fallbackText, key); // degrade to plain text reply if ref exists later\n}","handlingStrategy":"validation","validationCode":"const ref = conversationRefFromEvent(event) || gateway.config.conversationRef; if (!ref) { /* skip card, send plain fallback, or alert */ }","typeGuard":"function canSendCardToEvent(event, config) { return Boolean(conversationRefFromEvent(event) || config.conversationRef); }","tryCatchPattern":"try { await gateway.sendCardToEvent(event, card, fallback, key); } catch (e) { if (/no conversationRef is available for card/i.test(String(e))) { await gateway.sendReplyToEvent(event, fallback, key).catch(() => {}); } else throw e; }","preventionTips":["Configure conversationRef fallback for card responses","Ensure event payloads pass through the gateway unmodified","Degrade to fallbackText replies when card routing is unavailable"],"tags":["bot-gateway","card","conversation-ref","event-payload"],"backgroundTag":"missing-destination-address","analyzedSha":"99f24806c6a2c660b16e53e95211c517448a6c90","analyzedAt":"2026-08-27T04:11:01.184Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}