eyaltoledano/claude-task-master · critical

Failed to parse AI response as JSON: ${error.message}

Error message

Failed to parse AI response as JSON: ${error.message}

What it means

When JSON path extraction fails, the parser tries the fallbackItemExtractor; if that also throws, handleFallbackError decides whether to surface the failure. If no items were parsed from streaming at all, it throws 'Failed to parse AI response as JSON: <reason>'. This means the AI output contained no parseable JSON matching any jsonPath and no items were recovered.

Source

Thrown at src/utils/stream-parser.js:318

		// Only add items we haven't already parsed
		const itemsToAdd = fallbackItems.slice(
			this.progressTracker.parsedItems.length
		);
		const newItems = [];

		for (const item of itemsToAdd) {
			if (this.config.itemValidator(item)) {
				newItems.push(item);
				this.progressTracker.addItem(item);
			}
		}

		return newItems;
	}

	handleFallbackError(error) {
		if (this.progressTracker.parsedItems.length === 0) {
			throw new Error(`Failed to parse AI response as JSON: ${error.message}`);
		}
		// If we have some items from streaming, continue with those
	}
}

/**
 * Buffer size validator
 */
class BufferSizeValidator {
	constructor(maxSize) {
		this.maxSize = maxSize;
		this.currentSize = 0;
	}

	validateChunk(existingText, newChunk) {
		const newSize = Buffer.byteLength(existingText + newChunk, 'utf8');

		if (newSize > this.maxSize) {

View on GitHub (pinned to c0c98d367c)

Solutions

  1. Log/inspect error.message from the thrown error to see the underlying JSON.parse failure and the raw response.
  2. Check jsonPaths matches the actual response structure (use the raw text to verify).
  3. Add/adjust the system prompt to force valid JSON-only output, or parse a fenced ```json block in fallbackItemExtractor.
  4. Increase max tokens or handle truncation so the JSON is not cut off mid-stream.

Example fix

// before
const parser = new StreamParser({ jsonPaths: ['$.items'] });
// after
const parser = new StreamParser({
  jsonPaths: ['$.items', '$.data.items'], // cover actual response shapes
  fallbackItemExtractor: (text) => {
    const m = text.match(/```json\n([\s\S]*?)```/);
    return m ? JSON.parse(m[1]).items : [];
  }
});
Defensive patterns

Strategy: try-catch

Validate before calling

function looksLikeJson(text) {
  const t = text.trim();
  return t.startsWith('{') || t.startsWith('[') || /```json/.test(t);
}
// check accumulated stream text before final parse; if !looksLikeJson(text), surface/prompt-retry early

Try / catch

try {
  const items = await parser.parseStream(stream);
  return items;
} catch (err) {
  if (err.message.startsWith('Failed to parse AI response as JSON')) {
    // no items recovered: retry with stricter JSON prompt or inspect raw text
    logger.error('AI response not parseable', err.message);
    return retryWithJsonOnlyPrompt();
  }
  throw err;
}

Prevention

When it happens

Trigger: The model returned prose/markdown with no JSON; the response was truncated mid-JSON due to max tokens; jsonPath does not match the actual response shape (e.g. '$.items' but response nests under '$.data.items'); streaming produced zero items and then a JSON.parse error occurred during final parsing.

Common situations: Changing the prompt so the model stops emitting JSON; asking for output in a different shape than the configured jsonPaths; truncated responses from token limits; the stream erroring before any items were emitted so there is nothing to fall back on.

Understand the failure class

Related errors


AI-assisted analysis of eyaltoledano/claude-task-master@c0c98d367c (2026-08-29). Data as JSON: /api/errors/a92f931bfaabff9f. Report an issue: GitHub.