thedotmack/claude-mem · warning

SDK turn failed before emitting text, re-queueing the…

Error message

SDK turn failed before emitting text, re-queueing the claimed batch

What it means

Logged when an SDK turn completes with an error result subtype before any text was dispatched, so the claimed message batch is re-queued to pending instead of being dropped. Exactly one such retry happens per generator pass to avoid an endless failing turn spinning on the buffer.

Solutions

  1. Inspect resultSubtype in the log to identify the underlying SDK failure and fix its root cause.
  2. Retry the session after the transient condition clears; the batch is already back to pending.
  3. If failures persist, check Claude SDK/CLI version compatibility and network/API status.
  4. Remember only one automatic retry happens per pass — a second failing turn is processed as an error, not re-queued.
Defensive patterns

Strategy: retry

Try / catch

try {
  await provider.startSession(session);
} catch (e) {
  // batch was already reset to pending; re-poll the queue instead of discarding
  await sessionManager.pollPending(session.sessionDbId);
}

Prevention

When it happens

Trigger: In startSession's result handling, when resultIsError is true, turnDispatchedText is false, and retriedAfterErrorResult is false (first occurrence); resetProcessingToPending puts the claimed batch back in the queue.

Common situations: Transient SDK/CLI failures (crash, bad API response) on the very first turn of a batch; overloaded Claude API returning error results; a malformed prompt that fails before any assistant output.

Understand the failure class

Background: "API request failed": what wrapped HTTP errors from external APIs mean and how to find the real cause — this error's family across 29 libraries.

Related errors


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

Appendix: source

Thrown at src/services/worker/ClaudeProvider.ts:549

                  : undefined,
            });
          }

          const resultSubtype = (message as any).subtype as string | undefined;
          const resultIsError = (message as any).is_error === true || resultSubtype !== 'success';

          // The turn is over and the model never emitted text. Only a
          // successful turn means "the model read the batch and chose to skip
          // it" — forward the empty response once so the claim is acknowledged
          // instead of being retried forever. A failed turn never reached that
          // judgement, so its batch goes back to the buffer for the drain to
          // re-yield. Exactly one such retry per generator pass: a message is
          // always pending while a batch is re-queued, so the buffer never
          // idles out, and an endlessly failing turn would spin on it.
          if (!turnDispatchedText) {
            if (resultIsError && !retriedAfterErrorResult) {
              retriedAfterErrorResult = true;
              logger.warn('SDK', 'SDK turn failed before emitting text, re-queueing the claimed batch', {
                sessionId: session.sessionDbId,
                subtype: resultSubtype,
              });
              await this.sessionManager.resetProcessingToPending(session.sessionDbId);
            } else {
              await processAgentResponse(
                '',
                session,
                this.dbManager,
                this.sessionManager,
                worker,
                (session.cumulativeInputTokens + session.cumulativeOutputTokens) - discoveryTokenBaseline,
                session.earliestPendingTimestamp,
                'SDK',
                cwdTracker.lastCwd,
                modelId,
                activeResponseContext.current
              );

View on GitHub (pinned to d8bc9755e7)