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

  1. Refresh the task list on failure — the real state (running vs paused vs gone) resolves most 409s.
  2. Port the _runNow pattern: read the response body and include the backend detail and status in the error.
  3. Re-login on 401; verify ownership on 403.
  4. 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

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.