odysseus-dev/odysseus · error · Error
Failed to trigger task (${res.status})
Error message
Failed to trigger task (${res.status}) What it means
Thrown by the tasks UI when POST /api/tasks/{id}/run (trigger) returns a non-2xx status. The client first tries to parse the backend's JSON error detail and use it as the message; this literal message is the fallback when the response body has no parseable `detail` field. A 409 is special-cased to 'Task is already running' regardless of body.
Source
Thrown at static/js/tasks.js:163
});
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);
}
}
async function _stopTask(id) {
const res = await fetch(`${API_BASE}/api/tasks/${id}/stop`, {
method: 'POST',
credentials: 'same-origin',
});
if (!res.ok) {
let msg = `Failed to stop task (${res.status})`;
try {
const data = await res.json();
if (data && data.detail) msg = data.detail;
} catch (_) {}
throw new Error(msg);
}
}
View on GitHub (pinned to f9235ebbf1)
Solutions
- Check res.status in the thrown error or devtools network tab: 404 = stale task id (refresh the task list), 401/403 = re-login, 500 = inspect server logs
- Verify the task id still exists by re-fetching GET /api/tasks before triggering
- Confirm the page origin matches API_BASE (window.location.origin) so the same-origin cookie is sent
- If behind a proxy, ensure /api/tasks/* is routed to the app and error responses stay JSON
Example fix
// before
throw new Error(msg); // msg falls back to 'Failed to trigger task (status)'
// after — include endpoint context so the failing call is identifiable
throw new Error(msg === `Failed to trigger task (${res.status})` ? `Failed to trigger task ${id} (${res.status} ${res.statusText})` : msg); Defensive patterns
Strategy: try-catch
Validate before calling
// Verify task exists before triggering
const tasks = await (await fetch(`${API_BASE}/api/tasks`, { credentials: 'same-origin' })).json();
if (!tasks.tasks?.some(t => t.id === id)) { uiModule?.showError('Task no longer exists'); return; } Try / catch
try { await _triggerTask(id); } catch (e) { if (/\(409\)|already running/.test(e.message)) { /* benign */ } else if (/\(40[13]\)/.test(e.message)) { await refreshLoginOrTasks(); } else { uiModule?.showError(e.message); } } Prevention
- Refresh the task list before acting on tasks shown for a while
- Keep API_BASE same-origin with the page so cookies are sent
- Treat 409 as informational, not an error
When it happens
Trigger: Clicking Run/Trigger on a task whose backend run endpoint fails: task id no longer exists (404), auth/session cookie expired (401/403), backend scheduler error (500), or a non-JSON error body (e.g. HTML from a proxy) so data.detail is missing.
Common situations: Stale task list in the browser after tasks were redefined on the server; reverse proxy intercepting the API route; session cookie expired because credentials are 'same-origin' but the page was loaded from a different origin.
Related errors
- Failed to stop task (${res.status})
- Failed to revert task
- HTTP ${res.status}
- Failed to save note
- Failed to create task
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/9453291edfb352b4.
Report an issue: GitHub.