CherryHQ/cherry-studio · error · ModelscopeApiError
ModelScope API error: ${response.status} - ${errorText}
Error message
ModelScope API error: ${response.status} - ${errorText} What it means
ModelscopeApiError is thrown by ModelscopeTransport.request() whenever the ModelScope (api-inference.modelscope.cn) HTTP response has a non-ok status. It is a custom Error subclass that carries the originating HTTP status code on `error.statusCode` so callers (e.g. the poll loop) can classify terminal vs transient failures via isTerminalHttpStatus. The message embeds both the status and the first 500 chars of the response body for diagnostics.
Source
Thrown at src/main/ai/provider/custom/modelscope/modelscopeTransport.ts:230
const fetchOptions: RequestInit = {
method,
headers: {
Authorization: `Bearer ${this.apiKey}`,
...(method === 'POST' && { 'Content-Type': 'application/json' }),
...options.extraHeaders
},
signal: controller.signal
}
if (method === 'POST' && body !== undefined) {
fetchOptions.body = JSON.stringify(body)
}
try {
const response = await fetch(`${this.baseURL}${path}`, fetchOptions)
if (!response.ok) {
const errorText = (await response.text().catch(() => '')).slice(0, 500)
throw new ModelscopeApiError(`ModelScope API error: ${response.status} - ${errorText}`, response.status)
}
return (await response.json()) as T
} catch (error) {
if (error instanceof Error && error.name === 'AbortError') {
if (externallyAborted) throw createAbortError('ModelScope API request aborted')
throw new Error(`ModelScope API request timeout after ${timeout / 1000}s`)
}
throw error
} finally {
clearTimeout(timeoutId)
externalSignal?.removeEventListener('abort', onExternalAbort)
}
}
}
export function createModelscopeTransport(settings: ModelscopeTransportSettings): ModelscopeTransport {
return new ModelscopeTransport(settings)
}View on GitHub (pinned to 726446b54c)
Solutions
- Read error.statusCode and error.message: if 401/403, fix the API key in provider settings; if 429, back off and respect quota; if 400/422, inspect the embedded errorText for the offending field.
- Verify the model id is still listed in ModelScope's api-inference catalog and matches what the registry sends.
- If status >= 500 or 429, treat as transient and retry (the poll loop already does this for up to maxTransientRetries; for submit, add an outer retry).
- Confirm baseURL is the api-inference host (default https://api-inference.modelscope.cn) and not the model repos host.
Example fix
// before
await this.request('/v1/images/generations', 'POST', body, { timeout: 120000 })
// after — classify terminal vs transient at the call site
try {
await this.request('/v1/images/generations', 'POST', body, { timeout: 120000 })
} catch (e) {
if (e instanceof ModelscopeApiError && isTerminalHttpStatus(e.statusCode)) throw e
// transient: surface to user with retry option
} Defensive patterns
Strategy: try-catch
Validate before calling
// Validate key + baseURL before submit
if (!settings.apiKey) throw new Error('ModelScope apiKey is required')
if (!/^https?:\/\//.test(baseURL)) throw new Error(`Invalid ModelScope baseURL: ${baseURL}`) Type guard
export function isModelscopeApiError(e: unknown): e is { statusCode: number; message: string } {
return e instanceof Error && e.name === 'ModelscopeApiError' && typeof (e as any).statusCode === 'number'
} Try / catch
try {
await transport.submit(input)
} catch (e) {
if (isModelscopeApiError(e) && isTerminalHttpStatus(e.statusCode)) {
// 4xx (not 429): surface, do not retry
throw e
}
// 5xx / 429: retry with backoff
await backoffRetry(() => transport.submit(input))
} Prevention
- Always set and validate the ModelScope apiKey before constructing the transport.
- Use the default api-inference.modelscope.cn baseURL unless you have a known override.
- Catch ModelscopeApiError and key off statusCode to separate terminal from transient failures.
When it happens
Trigger: POST /v1/images/generations or GET /v1/tasks/{id} returns 4xx/5xx. Concrete causes: missing/wrong API key (401/403), exhausted free-tier quota (429), unknown model id in the body (400/404), malformed request body (422), or a 5xx during vendor outage. The submit call uses X-ModelScope-Async-Mode:true; omitting or mis-encoding size/steps/guidance triggers 400.
Common situations: First-run with an unset/typo'd apiKey, wrong baseURL override, model id drift after ModelScope renames a model, size string not in WxH format, guidance/steps sent under wrong camelCase spelling, or rate-limit hits during batch generation.
Related errors
- PPIO API error: ${response.status} - ${errorText}
- Brave API error: ${response.status} ${response.statusText}\n
- Brave API error: ${webResponse.status} ${webResponse.statusT
- ModelScope API request timeout after ${timeout / 1000}s
- responseBody || response.statusText
AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12).
Data as JSON: /api/errors/7e296c7e2d797935.
Report an issue: GitHub.