{"record":{"id":"b7a4e6bf632b32b4","repo":"BigPizzaV3/CodexPlusPlus","slug":"api-math-round-timeoutms-1000","errorCode":null,"errorMessage":"API 请求超时（${Math.round(timeoutMs/1000)} 秒），本批未提交","messagePattern":"API 请求超时（(.+?) 秒），本批未提交","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"tools/conversation-canvas/public/canvas.user.js","lineNumber":738,"sourceCode":"    signal?.throwIfAborted();\n    const profile=`external:${config.revision}`,slot=profile,key=`${profile}:${requestId}`;\n    if(session.profile!==profile){for(const name of Object.keys(session))delete session[name];session.profile=profile;}\n    for(let attempt=0;;attempt++){\n      signal?.throwIfAborted();\n      let entry=requests.get(slot);\n      if(entry?.key!==key){\n        entry?.controller.abort(closed());\n        const resumed=session.requestId===requestId&&session.phase==='submitted';\n        session.requestId=requestId;session.phase='submitted';await onSession();signal?.throwIfAborted();\n        const controller=new AbortController(),started=Date.now();\n        entry={key,controller,promise:null,chars:0,bytes:0,started};\n        const activeEntry=entry;\n        const timer=setTimeout(()=>controller.abort(Object.assign(Error(`API 请求超时（${Math.round(timeoutMs/1000)} 秒），本批未提交`),{canvasApiLocal:true,retryable:true})),timeoutMs);\n        entry.promise=(async()=>{\n          let response;\n          try{\n            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);\n          }catch(error){if(controller.signal.aborted)throw controller.signal.reason;throw apiError(error);}\n          return completionText(response);\n        })().finally(()=>clearTimeout(timer));\n        entry.promise.catch(()=>{if(requests.get(slot)===activeEntry)requests.delete(slot);});\n        requests.set(slot,entry);\n        progress(resumed?'重新发送未收到结果的批次（接口可能重复计费）…':`正在通过 ${config.model} 整理…`);\n      }\n      const report=()=>progress(`${config.model} · ${entry.chars?`已生成 ${entry.chars} 字符`:entry.bytes?'服务端已连接，等待结果':'等待 API 响应'} · 已用 ${Math.floor((Date.now()-entry.started)/1000)} 秒${attempt?` · 重试 ${attempt}/${maxRetries}`:''}`);\n      report();const heartbeat=setInterval(report,1000);\n      try{return await waitForApi(entry.promise,signal);}\n      catch(error){\n        clearInterval(heartbeat);\n        signal?.throwIfAborted();\n        if(!error.retryable||attempt>=maxRetries)throw error;\n        const delay=retryDelayMs*2**attempt;\n        progress(`${error.message}；${Math.ceil(delay/1000)} 秒后自动重试 ${attempt+1}/${maxRetries}（可能重复计费）…`);\n        // Pausing during backoff must not submit another request.\n        let timer;try{await waitForApi(new Promise(resolve=>{timer=setTimeout(resolve,delay);}),signal);}finally{clearTimeout(timer);}\n      }finally{clearInterval(heartbeat);}","sourceCodeStart":720,"sourceCodeEnd":756,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6/tools/conversation-canvas/public/canvas.user.js#L720-L756","documentation":"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.","triggerScenarios":"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.","commonSituations":"Slow or overloaded provider; very long prompt causing long generation; network latency/proxy issues; timeout configured too short for large batches.","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"],"exampleFix":"// before\ntimeoutMs = 30000\n// after\ntimeoutMs = 120000","handlingStrategy":"retry","validationCode":"// ensure timeoutMs > expected generation time: timeoutMs >= batchSize * estMsPerMessage","typeGuard":null,"tryCatchPattern":"try{ await organizeLong(...) }catch(e){ if(e?.retryable && e.message.includes('超时')){ backoffThenRetry(); } }","preventionTips":["Set timeouts generously for large batches","Use faster providers/speed profiles","Monitor provider latency and status pages"],"tags":["timeout","network","abort"],"backgroundTag":"request-timeout","analyzedSha":"b1ed92e5e4a2d74095d4b8db5af43cef7acba9c6","analyzedAt":"2026-09-19T23:35:21.129Z","contentChangedAt":"2026-09-19T23:35:21.129Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}