CherryHQ/cherry-studio · error · DashScopeApiError
0
0
Error message
DashScope async submit returned no task_id
What it means
Thrown as a `DashScopeApiError` (statusCode `0`, meaning not a real HTTP failure) when an async submit returns HTTP 2xx but `response.output.task_id` is missing. A well-formed async DashScope submit must return a task id to poll; its absence means the response shape does not match the async contract — likely a routing mismatch (a sync model treated as async) or an undocumented API change.
Source
Thrown at src/main/ai/provider/custom/dashscope/dashscopeTransport.ts:399
throw new Error(`Missing modelDescriptor for DashScope model: ${input.modelId}`)
}
const body = buildRequestBody(input, descriptor)
const extraHeaders: Record<string, string> = descriptor.isSync ? {} : { 'X-DashScope-Async': 'enable' }
const response = await this.request<DashScopeTaskResult>(descriptor.endpoint, 'POST', body, {
timeout: 120000,
signal: input.signal,
extraHeaders
})
if (descriptor.isSync) {
return { imageUrls: extractImageUrls(response.output, responseFamilyFor(descriptor)) }
}
const taskId = response.output.task_id
if (!taskId) {
throw new DashScopeApiError('DashScope async submit returned no task_id', 0)
}
this.pendingDescriptors.set(taskId, descriptor)
return { taskId }
}
async poll(
taskId: string,
options: {
signal?: AbortSignal
onProgress?: (progress: number) => void
modelDescriptor?: ImageTransportDescriptor
}
): Promise<string[]> {
// On a cross-restart resume the in-memory descriptor is gone (the transport is
// rebuilt without the submit-time `pendingDescriptors` entry); fall back to the
// descriptor persisted in the job input so the response family is still
// resolved correctly instead of defaulting to `results`.
const descriptor = this.pendingDescriptors.get(taskId) ?? options.modelDescriptorView on GitHub (pinned to 726446b54c)
Solutions
- Verify the model's `isSync` flag in the registry matches its actual response behavior — sync models return images directly, not a task id.
- Log the raw submit response body to confirm the envelope shape, then align the descriptor's endpoint/isSync.
- If DashScope changed the envelope, update the `DashScopeTaskOutput.task_id` extraction path.
Example fix
// before — descriptor marked async for a sync model
{ id: 'z-image-turbo', endpoint: '/api/v1/services/aigc/multimodal-generation/generation' /* isSync missing */ }
// after
{ id: 'z-image-turbo', endpoint: '/api/v1/services/aigc/multimodal-generation/generation', isSync: true } Defensive patterns
Strategy: try-catch
Validate before calling
const response = await transport.submit(input)
if (!descriptor.isSync && !response.taskId) {
throw new Error(`Expected task_id from ${descriptor.endpoint} but got none — verify isSync routing`)
} Try / catch
try {
const { taskId } = await transport.submit(input)
} catch (e) {
if (e instanceof DashScopeApiError && e.statusCode === 0 && /no task_id/.test(e.message)) {
// routing mismatch — re-check descriptor.isSync / endpoint
}
throw e
} Prevention
- Keep the registry's isSync flag aligned with each model family's real response shape
- Log the raw submit response when integrating a new model family
- Add an integration test asserting async submits return a task_id
When it happens
Trigger: The descriptor's `isSync` flag is false but the endpoint actually returns synchronously (no task_id); DashScope changed its async response envelope; the wrong endpoint path was used for the model family.
Common situations: A model family was misclassified as async in the registry's `vendorTransport`; DashScope deprecated an endpoint and returns a different body; a sync model's descriptor `isSync` was left unset.
Related errors
- DashScope task ${status.toLowerCase()}
- Task polling timeout
- QQ API request failed ${endpoint}: HTTP ${response.status} -
- Unknown chat type: ${type}
- Brave API error: ${response.status} ${response.statusText}\n
AI-assisted analysis of CherryHQ/cherry-studio@726446b54c (2026-08-12).
Data as JSON: /api/errors/dfd87e7d7f6d8d4e.
Report an issue: GitHub.