toeverything/AFFiNE · error · BadRequestException
Failed transcript tasks cannot be settled
Error message
Failed transcript tasks cannot be settled
What it means
settleTask only settles tasks whose status is 'ready' (or already 'settled', which is idempotent). A task with status 'failed' has no successful transcription result to settle, so the service rejects the call with a plain BadRequestException before touching the database.
Source
Thrown at packages/backend/server/src/plugins/copilot/transcript/service.ts:480
return { id: task.id, status: AiJobStatus.pending, infos };
}
async retryTask(userId: string, workspaceId: string, taskId: string) {
return await this.retry.retryTask(userId, workspaceId, taskId);
}
async settleTask(userId: string, workspaceId: string, taskId: string) {
const task = await this.models.copilotTranscriptTask.getWithUser(
userId,
workspaceId,
taskId
);
if (!task) {
throw new CopilotTranscriptionJobNotFound();
}
if (task.status === 'failed') {
throw new BadRequestException(
'Failed transcript tasks cannot be settled'
);
}
if (task.status !== 'ready' && task.status !== 'settled') {
return null;
}
if (task.status === 'settled') {
return taskToJob(task);
}
const settled = await this.models.copilotTranscriptTask.settle(task.id);
return taskToJob(settled);
}
async queryTask(
userId: string,
workspaceId: string,View on GitHub (pinned to 591f874dad)
Solutions
- Fetch the job and only call settle when status is 'ready' (re-settling 'settled' is also safe)
- If status is 'failed', call retryTask instead - after a successful retry the task becomes ready and can be settled
- In pollers, re-check status immediately before settling to shrink the race window
Example fix
// before
await service.settleTask(userId, workspaceId, taskId);
// after
const job = await service.getTask(userId, workspaceId, taskId);
if (job?.status === 'failed') {
await service.retryTask(userId, workspaceId, taskId);
} else if (job?.status === 'ready' || job?.status === 'settled') {
await service.settleTask(userId, workspaceId, taskId);
} Defensive patterns
Strategy: validation
Validate before calling
const job = await client.getTranscriptTask(workspaceId, taskId);
if (job.status === 'failed') {
await client.retryTranscriptTask(workspaceId, taskId);
} else if (job.status === 'ready' || job.status === 'settled') {
await client.settleTranscriptTask({ workspaceId, taskId });
} // pending/running: poll again later Type guard
const isSettleable = (status: string): status is 'ready' | 'settled' => status === 'ready' || status === 'settled';
Try / catch
try {
await settle(taskId);
} catch (e) {
if (e instanceof BadRequestException && /cannot be settled/.test(e.message)) {
await retryTask(taskId); // failed task: retry, then settle on ready
} else throw e;
} Prevention
- Model the task lifecycle (pending -> ready/failed -> settled) in the client and gate settle on it
- Poll status immediately before settling to avoid stale-state races
- Show a retry affordance instead of settle for failed jobs in the UI
When it happens
Trigger: Calling settle on a task whose dispatch previously errored (status 'failed'), e.g. after a transcription provider failure or native action crash; a poll-then-settle loop that settles whatever task it observes without checking status; racing settle against a task that fails concurrently.
Common situations: Background worker blindly settles every task it polls; UI settle button enabled for failed jobs; retry-and-settle race where the failure lands between the status check and the settle call.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- copilot_transcription_job_not_found
- transcript native recipe failed
- blob_not_found
- copilot_prompt_invalid
- copilot_session_deleted
AI-assisted analysis of toeverything/AFFiNE@591f874dad (2026-08-18).
Data as JSON: /api/errors/5d8cad4826f8c53a.
Report an issue: GitHub.