{"record":{"id":"4f6d1e5bd0654a51","repo":"odysseus-dev/odysseus","slug":"failed-to-resume-task","errorCode":null,"errorMessage":"Failed to resume task","messagePattern":"Failed to resume task","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"static/js/tasks.js","lineNumber":146,"sourceCode":"      badge.className = 'task-card-delete-busy';\n      badge.innerHTML = '<span class=\"task-card-delete-busy-label\">Deleting</span><span class=\"task-card-delete-busy-spin\" aria-hidden=\"true\"></span>';\n      card.appendChild(badge);\n    }\n  }\n}\n\nasync function _pauseTask(id) {\n  const res = await fetch(`${API_BASE}/api/tasks/${id}/pause`, {\n    method: 'POST', credentials: 'same-origin',\n  });\n  if (!res.ok) throw new Error('Failed to pause task');\n}\n\nasync function _resumeTask(id) {\n  const res = await fetch(`${API_BASE}/api/tasks/${id}/resume`, {\n    method: 'POST', credentials: 'same-origin',\n  });\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  }","sourceCodeStart":128,"sourceCodeEnd":164,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/static/js/tasks.js#L128-L164","documentation":"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.","triggerScenarios":"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.","commonSituations":"Resume raced with an automatic run starting; task removed while paused; two tabs toggling pause/resume; session expiry overnight with the page open.","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."],"exampleFix":"// before\nconst res = await fetch(`${API_BASE}/api/tasks/${id}/resume`, {\n  method: 'POST', credentials: 'same-origin',\n});\nif (!res.ok) throw new Error('Failed to resume task');\n// after — same pattern _runNow already uses\nconst res = await fetch(`${API_BASE}/api/tasks/${id}/resume`, {\n  method: 'POST', credentials: 'same-origin',\n});\nif (!res.ok) {\n  let msg = `Failed to resume task (${res.status})`;\n  try { const d = await res.json(); if (d.detail) msg = d.detail; } catch (_) {}\n  throw new Error(msg);\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try { await _resumeTask(id); } catch (err) { await refreshTasks(); showError(err.message); }","preventionTips":["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."],"tags":["fetch","api","http","tasks","state-machine"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}