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
- Treat 404 as deleted — remove the card locally and refresh instead of erroring.
- Ensure the busy badge is removed in a finally block so failed deletes do not leave a stuck 'Deleting' state.
- Re-login on 401; on 403 refresh the list to reconcile ownership.
- 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
- Treat 404 as success for deletes and remove the card locally.
- Always clear the 'Deleting…' badge in a finally block.
- Disable the delete control while the request is in flight.
- Re-login on 401; reconcile with a list refresh on 403.
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.