odysseus-dev/odysseus · error · Error
Failed to update task
Error message
Failed to update task
What it means
Thrown by _updateTask (static/js/tasks.js:90) when PUT /api/tasks/{id} returns non-2xx; message is the generic 'Failed to update task' with no status. Tests (test_task_cookbook_admin_gate.py, test_task_chain_owner_scope.py) show this route enforces ownership and admin gating, so 403 is a first-class outcome, not an anomaly.
Source
Thrown at static/js/tasks.js:90
async function _createTask(data) {
const res = await fetch(`${API_BASE}/api/tasks`, {
method: 'POST',
credentials: 'same-origin',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
});
if (!res.ok) throw new Error('Failed to create task');
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();View on GitHub (pinned to f9235ebbf1)
Solutions
- Check status: 403 → you are not the owner, refresh to see current state; 404 → task gone, drop the card and reload; 422 → fix the flagged field.
- Re-login on 401.
- Include the status and server detail in the thrown message for diagnosability.
- Reload the task list after any 404/403 instead of retrying blindly.
Example fix
// before
if (!res.ok) throw new Error('Failed to update task');
// after
const d = await res.json().catch(() => ({}));
if (!res.ok) throw new Error(d.detail || `Failed to update task (HTTP ${res.status})`); Defensive patterns
Strategy: try-catch
Validate before calling
if (!id) { throw new Error('Task id required'); } Try / catch
try { await _updateTask(id, data); } catch (err) { if (/HTTP 404|Failed to update/.test(err.message)) await refreshTasks(); showError(err.message); } Prevention
- Refresh the task list after 404/403 instead of retrying the same PUT.
- Only edit tasks you own — the route enforces ownership server-side.
- Add status + detail to the error message.
- Resolve 422s by reading the FastAPI validation body.
When it happens
Trigger: Editing/saving a task card: PUT the task JSON. 404 when the task id no longer exists (deleted elsewhere), 403 when the user does not own the task or lacks rights, 422 on invalid field values, 401 on expired session.
Common situations: Task deleted in another tab or by the agent; non-owner trying to edit a shared instance; stale page holding an id that was recreated; schedule/action validation same as create.
Related errors
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/3e858608110c9dde.
Report an issue: GitHub.