{"record":{"id":"9453291edfb352b4","repo":"odysseus-dev/odysseus","slug":"failed-to-trigger-task-res-status","errorCode":null,"errorMessage":"Failed to trigger task (${res.status})","messagePattern":"Failed to trigger task \\((.+?)\\)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"static/js/tasks.js","lineNumber":163,"sourceCode":"  });\n  if (!res.ok) throw new Error('Failed to resume task');\n}\n\nasync function _runNow(id, force = false) {\n  const res = await fetch(`${API_BASE}/api/tasks/${id}/run${force ? '?force=true' : ''}`, {\n    method: 'POST', credentials: 'same-origin',\n  });\n  if (!res.ok) {\n    // Surface the backend's actual reason — 409 means \"already running\",\n    // 404 task missing, etc. Previously every error rendered as the same\n    // generic \"Failed to trigger task\", which hid the cause.\n    let msg = `Failed to trigger task (${res.status})`;\n    try {\n      const data = await res.json();\n      if (data && data.detail) msg = data.detail;\n    } catch (_) {}\n    if (res.status === 409) msg = 'Task is already running';\n    throw new Error(msg);\n  }\n}\n\nasync function _stopTask(id) {\n  const res = await fetch(`${API_BASE}/api/tasks/${id}/stop`, {\n    method: 'POST',\n    credentials: 'same-origin',\n  });\n  if (!res.ok) {\n    let msg = `Failed to stop task (${res.status})`;\n    try {\n      const data = await res.json();\n      if (data && data.detail) msg = data.detail;\n    } catch (_) {}\n    throw new Error(msg);\n  }\n}\n","sourceCodeStart":145,"sourceCodeEnd":181,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/static/js/tasks.js#L145-L181","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before\nthrow new Error(msg); // msg falls back to 'Failed to trigger task (status)'\n// after — include endpoint context so the failing call is identifiable\nthrow new Error(msg === `Failed to trigger task (${res.status})` ? `Failed to trigger task ${id} (${res.status} ${res.statusText})` : msg);","handlingStrategy":"try-catch","validationCode":"// Verify task exists before triggering\nconst tasks = await (await fetch(`${API_BASE}/api/tasks`, { credentials: 'same-origin' })).json();\nif (!tasks.tasks?.some(t => t.id === id)) { uiModule?.showError('Task no longer exists'); return; }","typeGuard":null,"tryCatchPattern":"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); } }","preventionTips":["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"],"tags":["http","frontend","tasks","api-client"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}