thedotmack/claude-mem · warning

Empty summary response, leaving queue intact

Error message

Empty ${this.providerName} summary response, leaving queue intact

What it means

Same guard as the observation path but for summary messages: when the summary response has no content and forwardEmptyMessageResponse is false, processAgentResponse is skipped and the queued summary request is left intact. It stops empty summaries from wiping or corrupting session state.

Solutions

  1. Check the summary model and prompt; try a model that reliably returns content.
  2. Enable forwardEmptyMessageResponse if empty summaries are acceptable to forward.
  3. Inspect the upstream finish reason / error fields in the raw response.
  4. The queue entry remains intact — re-run the summary after fixing the upstream cause.

Example fix

// before
summaryConfig = { model: "min-model" };
// after
summaryConfig = { model: "gpt-4o-mini" }; // model that returns content reliably
Defensive patterns

Strategy: fallback

Validate before calling

if (!summaryResponse?.content) {
  // leave summary queue intact and retry later
}

Type guard

const hasContent = (r: { content?: string | null }): r is { content: string } =>
  typeof r.content === 'string' && r.content.trim().length > 0;

Try / catch

if (!hasContent(summaryResponse) && !provider.forwardEmptyMessageResponse) {
  logger.warn('summary empty, will retry');
}

Prevention

When it happens

Trigger: In processSummaryMessage (called from runMessageLoop), summaryResponse.content is empty while forwardEmptyMessageResponse is not enabled.

Common situations: Summarization model returning blank output (context overflow, content filter); upstream API degraded; wrong model configured for summarization.

Understand the failure class

Background: "empty response", "returned no data", "empty embeddings": what HTTP 200-with-empty-body errors mean across libraries — this error's family across 36 libraries.

Related errors


AI-assisted analysis of thedotmack/claude-mem@d8bc9755e7 (2026-09-17). Data as JSON: /api/errors/944485c0a62d8a1d. Report an issue: GitHub.

Appendix: source

Thrown at src/services/worker/OpenAICompatibleProvider.ts:391

    }
    const summaryResponse = await this.query(session.conversationHistory, summaryConfig);

    let tokensUsed = 0;
    if (summaryResponse.content) {
      // Appended once, by processAgentResponse below — see processObservationMessage.
      tokensUsed = summaryResponse.tokensUsed || 0;
      session.cumulativeInputTokens += Math.floor(tokensUsed * 0.7);
      session.cumulativeOutputTokens += Math.floor(tokensUsed * 0.3);
      session.lastUsage = this.buildLastUsage(summaryResponse);
    }

    if (summaryResponse.content || this.forwardEmptyMessageResponse) {
      await processAgentResponse(
        summaryResponse.content || '', session, this.dbManager, this.sessionManager,
        worker, tokensUsed, originalTimestamp, this.providerName, lastCwd, summaryResponse.servedModel ?? summaryConfig.model, responseContext
      );
    } else {
      logger.warn('SDK', `Empty ${this.providerName} summary response, leaving queue intact`, {
        sessionId: session.sessionDbId
      });
    }
  }

  /**
   * Map a classified provider failure onto the abortReason category that keeps
   * buffered work alive.
   *
   * handleGeneratorExit finalizes the session — dropping whatever is buffered —
   * for every category outside its preserve list. Quota was only ever set by
   * the two PROACTIVE sites (the pre-request rate-limit guard and the
   * observer-text heuristic), so a real 429 coming back from the provider left
   * abortReason null and the session was torn down as if the failure were
   * fatal (#3700). These conditions clear on their own; the work should still
   * be there when they do.
   */
  private preservingAbortReason(error: ClassifiedProviderError): string | null {

View on GitHub (pinned to d8bc9755e7)