odysseus-dev/odysseus · error · Error

Failed to delete task

Error message

Failed to delete task

What it means

Thrown by _deleteTask (static/js/tasks.js:98) when DELETE /api/tasks/{id} returns non-2xx; generic 'Failed to delete task'. The UI shows a 'Deleting…' busy badge on the card while this runs, so a failure leaves the badge stuck unless the caller clears it. Like update, this route is ownership-gated server-side.

Source

Thrown at static/js/tasks.js:98

  return await res.json();
}

async function _updateTask(id, data) {
  const res = await fetch(`${API_BASE}/api/tasks/${id}`, {
    method: 'PUT',
    credentials: 'same-origin',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(data),
  });
  if (!res.ok) throw new Error('Failed to update task');
  return await res.json();
}

async function _deleteTask(id) {
  const res = await fetch(`${API_BASE}/api/tasks/${id}`, {
    method: 'DELETE', credentials: 'same-origin',
  });
  if (!res.ok) throw new Error('Failed to delete task');
}

function _taskCardById(id) {
  const safe = (window.CSS && CSS.escape) ? CSS.escape(String(id)) : String(id).replace(/"/g, '\\"');
  return document.querySelector(`.task-card[data-id="${safe}"]`);
}

function _animateTaskRemoval(ids) {
  const cards = ids.map(_taskCardById).filter(Boolean);
  if (!cards.length) return Promise.resolve();
  for (const card of cards) {
    card.style.maxHeight = `${Math.max(card.getBoundingClientRect().height, card.scrollHeight)}px`;
    card.classList.add('memory-tidy-removing');
  }
  return new Promise(resolve => setTimeout(resolve, 520));
}

function _setTaskCardsDeleting(ids, active) {

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Treat 404 as deleted — remove the card locally and refresh instead of erroring.
  2. Ensure the busy badge is removed in a finally block so failed deletes do not leave a stuck 'Deleting' state.
  3. Re-login on 401; on 403 refresh the list to reconcile ownership.
  4. Prevent double-fire by disabling the delete control while the request is pending.

Example fix

// before
if (!res.ok) throw new Error('Failed to delete task');
// after
if (res.status === 404) return; // already gone — treat as success
const d = await res.json().catch(() => ({}));
if (!res.ok) throw new Error(d.detail || `Failed to delete task (HTTP ${res.status})`);
Defensive patterns

Strategy: try-catch

Validate before calling

if (!_taskCardById(id)) { return; // card already gone — nothing to delete visually }

Try / catch

try { await _deleteTask(id); } catch (err) { /* remove busy badge in finally */ } finally { card.querySelector('.task-card-delete-busy')?.remove(); }

Prevention

When it happens

Trigger: Confirming task deletion: DELETE /api/tasks/{id}. 404 when already deleted (double-confirm, concurrent delete), 403 non-owner, 401 expired session, 500 on persistence error.

Common situations: Two tabs both deleting; agent loop removed the task; permission change mid-session; slow server letting the user click delete twice.

Related errors


AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14). Data as JSON: /api/errors/a97e5deb710892a2. Report an issue: GitHub.