BigPizzaV3/CodexPlusPlus · error · Error
API 请求超时( 秒),本批未提交
Error message
API 请求超时(${Math.round(timeoutMs/1000)} 秒),本批未提交 What it means
Each external API request is wrapped in a setTimeout-driven AbortController; if the fetch+stream does not finish within timeoutMs, the controller aborts and this Error (marked canvasApiLocal and retryable) is thrown, leaving the batch uncommitted.
Solutions
- Increase the request timeout setting in the script
- Retry the batch (the error is flagged retryable)
- Reduce batch size so requests complete faster
- Check provider status/network; try a faster speed profile (speed:'fast') or different provider
Example fix
// before timeoutMs = 30000 // after timeoutMs = 120000
Defensive patterns
Strategy: retry
Validate before calling
// ensure timeoutMs > expected generation time: timeoutMs >= batchSize * estMsPerMessage
Try / catch
try{ await organizeLong(...) }catch(e){ if(e?.retryable && e.message.includes('超时')){ backoffThenRetry(); } } Prevention
- Set timeouts generously for large batches
- Use faster providers/speed profiles
- Monitor provider latency and status pages
When it happens
Trigger: The POST to the external chat completions endpoint takes longer than timeoutMs (timeout expired → controller.abort with this error as signal.reason), which is then rethrown from the entry promise.
Common situations: Slow or overloaded provider; very long prompt causing long generation; network latency/proxy issues; timeout configured too short for large batches.
Understand the failure class
Background: Request timed out: what client-side request timeouts mean across libraries (Request timed out, TIMED_OUT, APITimeoutError) — this error's family across 39 libraries.
- Timeouts: ETIMEDOUT, deadlines, and hung requests — what actually expires when a request times out.
Related errors
AI-assisted analysis of BigPizzaV3/CodexPlusPlus@b1ed92e5e4 (2026-09-19).
Data as JSON: /api/errors/b7a4e6bf632b32b4.
Report an issue: GitHub.
Appendix: source
Thrown at tools/conversation-canvas/public/canvas.user.js:738
signal?.throwIfAborted();
const profile=`external:${config.revision}`,slot=profile,key=`${profile}:${requestId}`;
if(session.profile!==profile){for(const name of Object.keys(session))delete session[name];session.profile=profile;}
for(let attempt=0;;attempt++){
signal?.throwIfAborted();
let entry=requests.get(slot);
if(entry?.key!==key){
entry?.controller.abort(closed());
const resumed=session.requestId===requestId&&session.phase==='submitted';
session.requestId=requestId;session.phase='submitted';await onSession();signal?.throwIfAborted();
const controller=new AbortController(),started=Date.now();
entry={key,controller,promise:null,chars:0,bytes:0,started};
const activeEntry=entry;
const timer=setTimeout(()=>controller.abort(Object.assign(Error(`API 请求超时(${Math.round(timeoutMs/1000)} 秒),本批未提交`),{canvasApiLocal:true,retryable:true})),timeoutMs);
entry.promise=(async()=>{
let response;
try{
response=await waitForApi(request(config.endpoint,{method:'POST',headers:{'Content-Type':'application/json',Authorization:`Bearer ${config.key}`},body:JSON.stringify(apiRequestBody(config,prompt)),signal:controller.signal,onProgress:info=>Object.assign(activeEntry,info)}),controller.signal);
}catch(error){if(controller.signal.aborted)throw controller.signal.reason;throw apiError(error);}
return completionText(response);
})().finally(()=>clearTimeout(timer));
entry.promise.catch(()=>{if(requests.get(slot)===activeEntry)requests.delete(slot);});
requests.set(slot,entry);
progress(resumed?'重新发送未收到结果的批次(接口可能重复计费)…':`正在通过 ${config.model} 整理…`);
}
const report=()=>progress(`${config.model} · ${entry.chars?`已生成 ${entry.chars} 字符`:entry.bytes?'服务端已连接,等待结果':'等待 API 响应'} · 已用 ${Math.floor((Date.now()-entry.started)/1000)} 秒${attempt?` · 重试 ${attempt}/${maxRetries}`:''}`);
report();const heartbeat=setInterval(report,1000);
try{return await waitForApi(entry.promise,signal);}
catch(error){
clearInterval(heartbeat);
signal?.throwIfAborted();
if(!error.retryable||attempt>=maxRetries)throw error;
const delay=retryDelayMs*2**attempt;
progress(`${error.message};${Math.ceil(delay/1000)} 秒后自动重试 ${attempt+1}/${maxRetries}(可能重复计费)…`);
// Pausing during backoff must not submit another request.
let timer;try{await waitForApi(new Promise(resolve=>{timer=setTimeout(resolve,delay);}),signal);}finally{clearTimeout(timer);}
}finally{clearInterval(heartbeat);}View on GitHub (pinned to b1ed92e5e4)