Skip to content

Instantly share code, notes, and snippets.

@ccarse
Last active March 26, 2026 17:49
Show Gist options
  • Select an option

  • Save ccarse/d88095dbc02ac096cfbb03d7af12a44b to your computer and use it in GitHub Desktop.

Select an option

Save ccarse/d88095dbc02ac096cfbb03d7af12a44b to your computer and use it in GitHub Desktop.
Minimal repro: OpenAI Responses API server_error during streaming with web_search tool
#!/usr/bin/env npx tsx
/**
* Minimal reproduction: OpenAI Responses API server_error during long-running
* streams with server-executed web_search.
*
* The error surfaces as a stream event mid-response:
* {"type":"error","sequence_number":N,"error":{"type":"server_error",...}}
*
* Root cause: a single Responses API call with many server-executed
* web_search tool calls eventually hits server_error. No tool loop, no
* context accumulation — just one long-running request.
*
* Observed at sequence_numbers: 235, 315, 460 (prod), 1488 (this script).
* Frequency: intermittent, ~1 in 10-15 runs.
*
* Request IDs for OpenAI investigation:
* req_efc5957215ea459d9b79599c14421820 (repro, 2026-03-26)
* req_ccd3232a3f6d4df681d605f13b43900f (prod, 2026-03-26)
* req_60d48e93f8384981a3a98107cd986ad3 (prod, 2026-03-25)
* req_62a1708d38f3431ca534205106f592e5 (prod, 2026-03-23)
*
* Usage:
* OPENAI_API_KEY=sk-... npx tsx scripts/repro-server-error.ts
*
* Requires: @ai-sdk/openai, ai (already in agent-zero deps)
*/
import { openai } from '@ai-sdk/openai';
import { convertToModelMessages, streamText, stepCountIs, type UIMessage } from 'ai';
const model = openai('gpt-5.2');
const tools = {
web_search: openai.tools.webSearch({}),
};
const SYSTEM_PROMPT = `You are a product research assistant with access to web_search.
Your task: research each product thoroughly. For EVERY product listed by the user:
- Use web_search to find current pricing, specs, and availability
- Search for each product individually — do not batch or skip any
Be thorough — do not skip any products. Research each one individually.`;
const USER_MESSAGE = `Research the following products and compile a detailed comparison report with pricing, specs, and availability for each:
1. Milwaukee M18 FUEL hammer drill
2. DeWalt 20V MAX XR impact driver
3. Makita 18V LXT circular saw
4. Bosch 12V Max drill/driver kit
5. Hilti TE 6-A22 rotary hammer
6. Festool TID 18 impact driver
7. Metabo HPT 36V MultiVolt circular saw
8. Ridgid 18V brushless router
9. Ryobi ONE+ HP compact router
10. Milwaukee M12 FUEL installation drill
11. DeWalt ATOMIC 20V MAX oscillating tool
12. Makita 40V Max XGT reciprocating saw
13. Bosch 18V brushless jigsaw
14. Hilti SF 6H-A22 drill driver
15. Milwaukee M18 FUEL Sawzall
For each product provide: current retail price, specs, weight, battery compatibility, and where to buy.`;
async function main() {
console.log(`Starting long-running tool-loop stream at ${new Date().toISOString()}`);
console.log('Model: gpt-5.2 (Responses API)');
console.log('Tools: web_search');
console.log('Message path: UIMessage[] → convertToModelMessages() → streamText()');
console.log('Expecting server_error intermittently (~1 in 10-15 runs).\n');
const startTime = Date.now();
let stepCount = 0;
const messages: UIMessage[] = [
{
id: 'msg_user_1',
role: 'user',
parts: [{ type: 'text', text: USER_MESSAGE }],
},
];
const modelMessages = await convertToModelMessages(messages, {
tools,
ignoreIncompleteToolCalls: true,
});
try {
const result = streamText({
model,
system: SYSTEM_PROMPT,
messages: modelMessages,
tools,
stopWhen: stepCountIs(80),
providerOptions: {
openai: {
reasoningSummary: 'auto',
reasoningEffort: 'low',
textVerbosity: 'low',
},
},
onStepFinish: ({ stepNumber, finishReason, toolCalls }) => {
stepCount = stepNumber;
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
const toolNames = toolCalls?.map((tc) => tc.toolName).join(', ') || 'none';
console.log(` [step ${stepNumber}] finishReason=${finishReason} tools=[${toolNames}] elapsed=${elapsed}s`);
},
onError: ({ error }) => {
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
console.error(`\n*** STREAM ERROR at ${elapsed}s (step ~${stepCount}) ***`);
console.error(JSON.stringify(error, null, 2));
},
});
// Ring buffer of last 20 events for debugging
const recentEvents: { type: string; summary: string }[] = [];
const RING_SIZE = 20;
for await (const part of result.fullStream) {
// Summarize each event compactly
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const p = part as any;
const summary =
p.type === 'text-delta'
? `"${String(p.textDelta).slice(0, 40)}…"`
: p.type === 'tool-call'
? `toolName=${p.toolName}`
: p.type === 'tool-result'
? `toolName=${p.toolName}`
: p.type === 'step-finish'
? `finishReason=${p.finishReason}`
: '';
recentEvents.push({ type: p.type, summary });
if (recentEvents.length > RING_SIZE) recentEvents.shift();
if (part.type === 'error') {
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
console.error(`\n*** ERROR PART at ${elapsed}s ***`);
console.error(JSON.stringify(part.error, null, 2));
console.error(`\nLast ${RING_SIZE} stream events before error:`);
for (const evt of recentEvents) {
console.error(` ${evt.type}${evt.summary ? ` — ${evt.summary}` : ''}`);
}
}
}
const finishReason = await result.finishReason;
const usage = await result.totalUsage;
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
console.log(`\nStream completed in ${elapsed}s`);
console.log(` finishReason: ${finishReason}`);
console.log(` steps: ${stepCount}`);
console.log(` totalUsage: ${JSON.stringify(usage)}`);
} catch (error) {
const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
console.error(`\n*** THROWN ERROR at ${elapsed}s (step ~${stepCount}) ***`);
if (error instanceof Error) {
console.error(` ${error.name}: ${error.message}`);
} else {
console.error(JSON.stringify(error, null, 2));
}
process.exit(1);
}
}
main();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment