{"record":{"id":"bf715cfd04c3cd1f","repo":"CherryHQ/cherry-studio","slug":"modelscope-api-request-timeout-after-timeout-1","errorCode":null,"errorMessage":"ModelScope API request timeout after ${timeout / 1000}s","messagePattern":"ModelScope API request timeout after (.+?)s","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/main/ai/provider/custom/modelscope/modelscopeTransport.ts","lineNumber":236,"sourceCode":"        ...options.extraHeaders\n      },\n      signal: controller.signal\n    }\n    if (method === 'POST' && body !== undefined) {\n      fetchOptions.body = JSON.stringify(body)\n    }\n\n    try {\n      const response = await fetch(`${this.baseURL}${path}`, fetchOptions)\n      if (!response.ok) {\n        const errorText = (await response.text().catch(() => '')).slice(0, 500)\n        throw new ModelscopeApiError(`ModelScope API error: ${response.status} - ${errorText}`, response.status)\n      }\n      return (await response.json()) as T\n    } catch (error) {\n      if (error instanceof Error && error.name === 'AbortError') {\n        if (externallyAborted) throw createAbortError('ModelScope API request aborted')\n        throw new Error(`ModelScope API request timeout after ${timeout / 1000}s`)\n      }\n      throw error\n    } finally {\n      clearTimeout(timeoutId)\n      externalSignal?.removeEventListener('abort', onExternalAbort)\n    }\n  }\n}\n\nexport function createModelscopeTransport(settings: ModelscopeTransportSettings): ModelscopeTransport {\n  return new ModelscopeTransport(settings)\n}\n\nexport type { ModelscopeTransport }\n","sourceCodeStart":218,"sourceCodeEnd":251,"githubUrl":"https://github.com/CherryHQ/cherry-studio/blob/726446b54cd69ffe51a276638672f6d95ca0768c/src/main/ai/provider/custom/modelscope/modelscopeTransport.ts#L218-L251","documentation":"Thrown when the internal AbortController (set by setTimeout for `timeout` ms) fires before the ModelScope fetch resolves, AND the abort did NOT originate from the caller's external signal. It is distinct from the externally-aborted path, which throws a synthesized AbortError instead. The message reports the configured timeout in seconds for diagnostics.","triggerScenarios":"request() is called with a `timeout` (DEFAULT_TIMEOUT for poll, 120000ms for submit, 10000ms for task polls) and the fetch neither resolves nor is externally aborted within that window. Happens during vendor slowness, model cold-load, DNS stalls, or a hung TCP connection behind a proxy.","commonSituations":"Cold-loading a large diffusion model on the ModelScope side exceeding the submit timeout, network throttling, corporate proxy buffering, or the user is on a slow link and 120s submit budget is too tight.","solutions":["Retry once with backoff — timeouts are frequently transient (network blip / cold load).","If submit consistently times out, raise the per-call timeout (submit uses 120000ms; pass a larger options.timeout for known-slow cold-start models).","Verify network/proxy reachability to api-inference.modelscope.cn; check whether a corporate proxy is silently dropping long-lived POSTs.","Confirm the user is not on a metered/offline connection."],"exampleFix":"// before\nconst r = await this.request(path, 'POST', body, { timeout: 120000 })\n// after — escalate timeout for known slow cold-start models\nconst r = await this.request(path, 'POST', body, { timeout: coldStartModel ? 300000 : 120000 })","handlingStrategy":"retry","validationCode":"// No request-time validation prevents a timeout; budget it\nconst timeout = coldStartModel ? 300000 : DEFAULT_TIMEOUT","typeGuard":"export function isModelscopeTimeoutError(e: unknown): boolean {\n  return e instanceof Error && /^ModelScope API request timeout after/.test(e.message)\n}","tryCatchPattern":"try {\n  await transport.submit(input)\n} catch (e) {\n  if (isModelscopeTimeoutError(e)) {\n    await backoffRetry(() => transport.submit(input), { maxAttempts: 2 })\n  }\n  throw e\n}","preventionTips":["Size the per-call timeout to the operation (submit needs a larger budget than poll).","Treat timeouts as transient and retry once before surfacing.","Monitor vendor status during long submit calls to give the user a cancel affordance."],"tags":["timeout","network","modelscope","abort","image-generation"],"backgroundTag":null,"analyzedSha":"726446b54cd69ffe51a276638672f6d95ca0768c","analyzedAt":"2026-08-12T17:30:37.448Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}