odysseus-dev/odysseus · error · Error
Failed to resume task
Error message
Failed to resume task
What it means
Thrown by _resumeTask (static/js/tasks.js:146) when POST /api/tasks/{id}/resume returns non-2xx. It is the exact mirror of _pauseTask (error 138) with the same blind spot: the HTTP status is dropped, so 'Failed to resume task' covers everything from 401 to 409. Its sibling _runNow (visible below it in the file) was already fixed to surface the backend reason — resume was not.
Source
Thrown at static/js/tasks.js:146
badge.className = 'task-card-delete-busy';
badge.innerHTML = '<span class="task-card-delete-busy-label">Deleting</span><span class="task-card-delete-busy-spin" aria-hidden="true"></span>';
card.appendChild(badge);
}
}
}
async function _pauseTask(id) {
const res = await fetch(`${API_BASE}/api/tasks/${id}/pause`, {
method: 'POST', credentials: 'same-origin',
});
if (!res.ok) throw new Error('Failed to pause task');
}
async function _resumeTask(id) {
const res = await fetch(`${API_BASE}/api/tasks/${id}/resume`, {
method: 'POST', credentials: 'same-origin',
});
if (!res.ok) throw new Error('Failed to resume task');
}
async function _runNow(id, force = false) {
const res = await fetch(`${API_BASE}/api/tasks/${id}/run${force ? '?force=true' : ''}`, {
method: 'POST', credentials: 'same-origin',
});
if (!res.ok) {
// Surface the backend's actual reason — 409 means "already running",
// 404 task missing, etc. Previously every error rendered as the same
// generic "Failed to trigger task", which hid the cause.
let msg = `Failed to trigger task (${res.status})`;
try {
const data = await res.json();
if (data && data.detail) msg = data.detail;
} catch (_) {}
if (res.status === 409) msg = 'Task is already running';
throw new Error(msg);
}View on GitHub (pinned to f9235ebbf1)
Solutions
- Refresh the task list on failure — the real state (running vs paused vs gone) resolves most 409s.
- Port the _runNow pattern: read the response body and include the backend detail and status in the error.
- Re-login on 401; verify ownership on 403.
- Disable the resume control while the request is in flight to avoid racing yourself.
Example fix
// before
const res = await fetch(`${API_BASE}/api/tasks/${id}/resume`, {
method: 'POST', credentials: 'same-origin',
});
if (!res.ok) throw new Error('Failed to resume task');
// after — same pattern _runNow already uses
const res = await fetch(`${API_BASE}/api/tasks/${id}/resume`, {
method: 'POST', credentials: 'same-origin',
});
if (!res.ok) {
let msg = `Failed to resume task (${res.status})`;
try { const d = await res.json(); if (d.detail) msg = d.detail; } catch (_) {}
throw new Error(msg);
} Defensive patterns
Strategy: try-catch
Try / catch
try { await _resumeTask(id); } catch (err) { await refreshTasks(); showError(err.message); } Prevention
- Port the _runNow error pattern (status + backend detail) to _resumeTask and _pauseTask.
- Re-fetch the list after failure — 409 usually means the state already changed.
- Guard against rapid pause/resume toggling with an in-flight flag.
- Re-login on 401; check ownership on 403.
When it happens
Trigger: Clicking resume on a paused task: POST /api/tasks/{id}/resume. 404 task deleted; 409 when the task is not paused (already running); 403 non-owner; 401 expired session; 500 scheduler error.
Common situations: Resume raced with an automatic run starting; task removed while paused; two tabs toggling pause/resume; session expiry overnight with the page open.
Related errors
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/4f6d1e5bd0654a51.
Report an issue: GitHub.