Problem
When the AI provider returns an empty stream (zero steps recorded), the Vercel AI SDK throws NoOutputGeneratedError with message "No output generated. Check the stream for errors.". This error is not retried by ProbeAgent, causing the entire check to fail on what is usually a transient provider hiccup.
Production example: task 70d0b650 — a simple "hi!" greeting to gemini-3.1-pro-preview resulted in an empty stream. The user saw:
"The AI provider rate limit was reached before a response could be generated."
Root Cause
The retry boundary is in the wrong place:
RetryManager.executeWithRetry() wraps the streamText() call (ProbeAgent.js:4429)
streamText() returns immediately — it doesn't throw
- The actual error surfaces later when
await result.steps is called (ProbeAgent.js:4468)
- This is outside the RetryManager scope — caught by the outer
catch at line 4711
isRetryableError() checks the error message against DEFAULT_RETRYABLE_ERRORS, but "No output generated" doesn't match any pattern (429, rate_limit, timeout, etc.)
RetryManager.executeWithRetry(streamText(...)) ← retry wraps this
→ streamText() returns immediately (OK) ← no error here
→ await result.steps ← NoOutputGeneratedError thrown HERE
→ caught at outer catch (line 4711) ← NOT retried
→ "Error: Failed to get response from AI model. No output generated."
Suggested Fix
Two changes needed:
1. Add "No output generated" to DEFAULT_RETRYABLE_ERRORS
In RetryManager.js:
const DEFAULT_RETRYABLE_ERRORS = [
'Overloaded',
'overloaded',
'rate_limit',
'rate limit',
'429',
'500',
'502',
'503',
'504',
'timeout',
'ECONNRESET',
'ETIMEDOUT',
'ENOTFOUND',
'api_error',
'No output generated', // ← add this
];
2. Wrap await result.steps in a retry loop
In ProbeAgent.js around lines 4464-4488, the executeAIRequest function should catch NoOutputGeneratedError from await result.steps and re-invoke streamTextWithRetryAndFallback:
const executeAIRequest = async () => {
const maxEmptyRetries = 2;
for (let emptyRetry = 0; emptyRetry <= maxEmptyRetries; emptyRetry++) {
const result = await this.streamTextWithRetryAndFallback(streamOptions);
// ... timeout setup ...
try {
const steps = await result.steps;
if (!steps || steps.length === 0) {
if (emptyRetry < maxEmptyRetries) {
console.log(`[WARN] Empty stream (0 steps), retrying (${emptyRetry + 1}/${maxEmptyRetries})...`);
continue;
}
}
// ... rest of text extraction ...
return { finalText, result };
} catch (err) {
if (err?.name === 'AI_NoOutputGeneratedError' && emptyRetry < maxEmptyRetries) {
console.log(`[WARN] NoOutputGeneratedError, retrying (${emptyRetry + 1}/${maxEmptyRetries})...`);
continue;
}
throw err;
}
}
};
Impact
Any transient provider hiccup that returns an empty stream (common with Gemini under load) kills the entire task with no retry. This is especially bad for simple queries that would succeed on retry.
Trace Evidence
ai.request.started: model=gemini-3.1-pro-preview, input_length=13316
ai.request.failed: error="No output generated. Check the stream for errors."
visor.ai_check.failed: error="Error: Failed to get response from AI model. No output generated."
visor.check.generate-response.failed: error="The AI provider rate limit was reached..."
Problem
When the AI provider returns an empty stream (zero steps recorded), the Vercel AI SDK throws
NoOutputGeneratedErrorwith message"No output generated. Check the stream for errors.". This error is not retried by ProbeAgent, causing the entire check to fail on what is usually a transient provider hiccup.Production example: task
70d0b650— a simple "hi!" greeting togemini-3.1-pro-previewresulted in an empty stream. The user saw:Root Cause
The retry boundary is in the wrong place:
RetryManager.executeWithRetry()wraps thestreamText()call (ProbeAgent.js:4429)streamText()returns immediately — it doesn't throwawait result.stepsis called (ProbeAgent.js:4468)catchat line 4711isRetryableError()checks the error message againstDEFAULT_RETRYABLE_ERRORS, but"No output generated"doesn't match any pattern (429,rate_limit,timeout, etc.)Suggested Fix
Two changes needed:
1. Add
"No output generated"toDEFAULT_RETRYABLE_ERRORSIn
RetryManager.js:2. Wrap
await result.stepsin a retry loopIn
ProbeAgent.jsaround lines 4464-4488, theexecuteAIRequestfunction should catchNoOutputGeneratedErrorfromawait result.stepsand re-invokestreamTextWithRetryAndFallback:Impact
Any transient provider hiccup that returns an empty stream (common with Gemini under load) kills the entire task with no retry. This is especially bad for simple queries that would succeed on retry.
Trace Evidence