{"record":{"id":"53b9a207568473de","repo":"thedotmack/claude-mem","slug":"empty-this-providername-observation-response-l","errorCode":null,"errorMessage":"Empty ${this.providerName} observation response, leaving queue intact","messagePattern":"Empty (.+?) observation response, leaving queue intact","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/services/worker/OpenAICompatibleProvider.ts","lineNumber":335,"sourceCode":"    if (obsResponse.content) {\n      // The assistant turn is appended once, by processAgentResponse below.\n      // Appending it here too stored every reply twice (#3619), inflating the\n      // window — and therefore every subsequent request — by ~50%.\n      tokensUsed = obsResponse.tokensUsed || 0;\n      session.cumulativeInputTokens += Math.floor(tokensUsed * 0.7);\n      session.cumulativeOutputTokens += Math.floor(tokensUsed * 0.3);\n      // Both sides or nothing: a backend reporting only one of the two counts\n      // must not produce a half-real event (input=0 → compression_ratio 0.0).\n      session.lastUsage = this.buildLastUsage(obsResponse);\n    }\n\n    if (obsResponse.content || this.forwardEmptyMessageResponse) {\n      await processAgentResponse(\n        obsResponse.content || '', session, this.dbManager, this.sessionManager,\n        worker, tokensUsed, originalTimestamp, this.providerName, lastCwd, obsResponse.servedModel ?? config.model, responseContext\n      );\n    } else {\n      logger.warn('SDK', `Empty ${this.providerName} observation response, leaving queue intact`, {\n        sessionId: session.sessionDbId\n      });\n    }\n  }\n\n  private async processSummaryMessage(\n    session: ActiveSession,\n    message: { last_assistant_message?: string },\n    worker: WorkerRef | undefined,\n    config: TConfig,\n    mode: ModeConfig,\n    originalTimestamp: number | null,\n    lastCwd: string | undefined\n  ): Promise<void> {\n    if (!session.memorySessionId) {\n      throw new Error('Cannot process summary: memorySessionId not yet captured. This session may need to be reinitialized.');\n    }\n","sourceCodeStart":317,"sourceCodeEnd":353,"githubUrl":"https://github.com/thedotmack/claude-mem/blob/d8bc9755e74915e5c3b999181e10a67c889bce2a/src/services/worker/OpenAICompatibleProvider.ts#L317-L353","documentation":"This warning is emitted when the provider receives an observation response with no content (and forwardEmptyMessageResponse is false), so processAgentResponse is skipped and the queue message is left intact rather than marked processed. It prevents empty model replies from consuming queued observation requests.","triggerScenarios":"In processObservationMessage (called from runMessageLoop), the upstream OpenAI-compatible API returns a response whose obsResponse.content is empty/undefined while forwardEmptyMessageResponse is not enabled.","commonSituations":"Model returning empty completions due to prompt or truncation issues; upstream provider outage or content filter stripping the reply; misconfigured base URL hitting an endpoint that returns empty bodies.","solutions":["Inspect the upstream response for why content is empty (finish reason, content filter, truncation).","Set forwardEmptyMessageResponse=true if empty replies should be forwarded to the agent pipeline.","Verify the base URL/model config points to a valid chat-completions endpoint.","Retry or re-check the queued observation message; it remains pending and will be reprocessed."],"exampleFix":"// before (provider config)\n{ \"providerName\": \"MyProvider\" }\n// after\n{ \"providerName\": \"MyProvider\", \"forwardEmptyMessageResponse\": true }","handlingStrategy":"fallback","validationCode":"if (!obsResponse || typeof obsResponse.content !== 'string' || obsResponse.content.length === 0) {\n  // skip processing or enable forwardEmptyMessageResponse\n}","typeGuard":"const hasContent = (r: { content?: string | null }): r is { content: string } =>\n  typeof r.content === 'string' && r.content.trim().length > 0;","tryCatchPattern":"if (!hasContent(obsResponse) && !provider.forwardEmptyMessageResponse) {\n  // queue stays intact; re-poll later\n}","preventionTips":["Set forwardEmptyMessageResponse if empty replies are acceptable","Log upstream finish reasons to catch content filters/truncation early","Verify base URL and model config for the OpenAI-compatible endpoint"],"tags":["empty-response","openai-compatible","queue"],"backgroundTag":"empty-api-response","analyzedSha":"d8bc9755e74915e5c3b999181e10a67c889bce2a","analyzedAt":"2026-09-17T16:40:26.182Z","contentChangedAt":"2026-09-17T16:40:26.182Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}