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

  1. Increase the request timeout setting in the script
  2. Retry the batch (the error is flagged retryable)
  3. Reduce batch size so requests complete faster
  4. 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

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.

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)