danny-avila/LibreChat · info · Error
Run cancelled
Error message
Run cancelled
What it means
Thrown by waitForRun when the ABORT_KEYS cache entry for the conversation is set to 'cancelled'. This is intentional control flow: a user (or system) marked the run for cancellation, so polling stops and the cancellation propagates up as an exception. It is a signal, not an infrastructure fault.
Source
Thrown at api/server/services/Runs/handle.js:135
if (run.status !== lastSeenStatus) {
logger.debug(`[${run.status}] ${runInfo}`);
lastSeenStatus = run.status;
}
logger.debug(`[heartbeat ${i}] ${runStatus}`);
let cancelStatus;
try {
const timeoutMessage = `[heartbeat ${i}] ${runIdLog} | Cancel Status check operation timed out.`;
cancelStatus = await withTimeout(cache.get(cacheKey), raceTimeoutMs, timeoutMessage);
} catch (error) {
logger.warn(`Error retrieving cancel status: ${error}`);
}
if (cancelStatus === 'cancelled') {
logger.warn(`[waitForRun] ${runStatus} | RUN CANCELLED`);
throw new Error('Run cancelled');
}
if (![RunStatus.IN_PROGRESS, RunStatus.QUEUED].includes(run.status)) {
logger.debug(`[FINAL] ${runInfo} | status: ${run.status}`);
await runManager.fetchRunSteps({
openai,
thread_id: thread_id,
run_id: run_id,
runStatus: run.status,
final: true,
});
break;
}
// may use in future; for now, just fetch from the final status
await runManager.fetchRunSteps({
openai,
thread_id: thread_id,View on GitHub (pinned to 5ff282f900)
Solutions
- Treat this exception as expected: catch it and report a 'cancelled' state to the client rather than logging it as an error.
- If the cancellation is spurious, clear the ABORT_KEYS entry for the conversation key before starting a new run.
- Ensure the cancel flag is written under the same key format `${userId}:${conversationId}` that waitForRun reads.
Example fix
// before
await waitForRun({ openai, run_id, thread_id, runManager });
// after
try {
await waitForRun({ openai, run_id, thread_id, runManager });
} catch (err) {
if (err.message === 'Run cancelled') {
return { status: 'cancelled' };
}
throw err;
} Defensive patterns
Strategy: try-catch
Validate before calling
const cancelStatus = await cache.get(`${userId}:${conversationId}`);
if (cancelStatus === 'cancelled') {
return { status: 'cancelled' };
} Type guard
const isCancelled = async (cache, key) => (await cache.get(key)) === 'cancelled';
Try / catch
try {
await waitForRun({ openai, run_id, thread_id, runManager });
} catch (err) {
if (err.message === 'Run cancelled') {
return { status: 'cancelled' };
}
throw err;
} Prevention
- Treat 'Run cancelled' as expected control flow, not an error.
- Clear the ABORT_KEYS entry for a conversation before starting a fresh run.
- Write cancel flags with the exact `${userId}:${conversationId}` key format.
When it happens
Trigger: The user clicks stop/abort on an in-progress assistant run; a parent flow sets cache.set(`${userId}:${conversationId}`, 'cancelled') under ABORT_KEYS; a stale cancel flag from a previous run on the same conversation key is still present.
Common situations: UI stop button; automated test that aborts; an earlier run on the same conversation was cancelled and the cache key was never cleared.
Related errors
- [${req.baseUrl}] Invalid version: ${version}
- Unexpected run status ${run.status}.\nFull run info:\n\n${ru
- no_user_key
- Assistants API key not provided. Please provide it again.
- [waitForRun] ${runIdLog} | Run retrieval failed after ${maxR
AI-assisted analysis of danny-avila/LibreChat@5ff282f900 (2026-08-12).
Data as JSON: /api/errors/05345f11c15c15f3.
Report an issue: GitHub.