{"record":{"id":"3f5ea73b5f2456bd","repo":"odysseus-dev/odysseus","slug":"failed-to-pause-task","errorCode":null,"errorMessage":"Failed to pause task","messagePattern":"Failed to pause task","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"static/js/tasks.js","lineNumber":139,"sourceCode":"    const existing = card.querySelector('.task-card-delete-busy');\n    if (!active) {\n      existing?.remove();\n      continue;\n    }\n    if (!existing) {\n      const badge = document.createElement('span');\n      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})`;","sourceCodeStart":121,"sourceCodeEnd":157,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/static/js/tasks.js#L121-L157","documentation":"Thrown by _pauseTask (static/js/tasks.js:139) when POST /api/tasks/{id}/pause returns non-2xx; generic 'Failed to pause task' hides the status. Pause is a state transition on a live task, so failures cluster around the task's current lifecycle state rather than validation.","triggerScenarios":"Clicking pause on a running task: POST /api/tasks/{id}/pause. 404 task deleted; 409 when the task is not in a pausable state (already paused, mid-run lock); 403 non-owner; 401 expired session; 500 scheduler error.","commonSituations":"Pause clicked while the task is between runs or already paused from another tab; task deleted since the list rendered; ownership mismatch.","solutions":["On failure, re-fetch the task list and re-render — the card's state label will show the true current state (paused/running) and usually explains the rejection.","Treat 'already paused' outcomes as success and sync UI state.","Add the status to the error message: `\\`Failed to pause task (HTTP ${res.status})\\``.","Re-login on 401; on 403 verify you own the task."],"exampleFix":"// before\nif (!res.ok) throw new Error('Failed to pause task');\n// after\nif (!res.ok) throw new Error(`Failed to pause task (HTTP ${res.status})`);","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try { await _pauseTask(id); } catch (err) { await refreshTasks(); // re-sync true state — most failures are state races showError(err.message); }","preventionTips":["After any pause failure, re-fetch the task list to see the real state before retrying.","Include r.status in the error string.","Disable the pause/resume toggle while a transition is in flight.","Treat 'not pausable' (409) outcomes as a UI-state refresh, not a hard error."],"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"}