odysseus-dev/odysseus · error · Error
Revoke failed
Error message
Revoke failed
What it means
Thrown when DELETE /api/tokens/{tokenId} (Revoke button) returns non-2xx. Unlike the PATCH handlers this handler never parses the response body, so the user always sees the generic 'Revoke failed' regardless of whether the server said 401, 404, or 403. The backend (routes/api_token_routes.py) returns 404 when the token id no longer exists and does not invalidate the cache in that case.
Source
Thrown at static/js/settings.js:5388
setTimeout(() => { formEl.style.display = 'none'; }, 350);
} catch (err) {
if (msg) { msg.textContent = err?.message || 'Save failed'; msg.style.color = 'var(--red)'; }
}
});
// Revoke = delete this agent token entirely. Confirmation prompt keeps
// it from being a one-click footgun. Closes the form on success.
el('uf-codex-revoke')?.addEventListener('click', async () => {
const tokenId = formEl.dataset.createdTokenId;
if (!tokenId) return;
const ok = window.styledConfirm
? await window.styledConfirm(`Revoke this ${cfg.word} agent token? Integrations using it will lose access.`, { confirmText: 'Revoke', danger: true })
: confirm(`Revoke this ${cfg.word} agent token? Integrations using it will lose access.`);
if (!ok) return;
const msg = el('uf-codex-msg');
try {
const r = await fetch(`/api/tokens/${tokenId}`, { method: 'DELETE', credentials: 'same-origin' });
if (!r.ok) throw new Error('Revoke failed');
if (msg) { msg.textContent = 'Revoked'; msg.style.color = 'var(--color-error)'; }
await renderList();
setTimeout(() => { formEl.style.display = 'none'; }, 350);
} catch (err) {
if (msg) { msg.textContent = err?.message || 'Revoke failed'; msg.style.color = 'var(--red)'; }
}
});
const _autoCreateCodex = async () => {
const msg = el('uf-codex-msg');
const prompt = el('uf-codex-prompt');
const pending = el('uf-codex-pending');
const createBtn = el('uf-codex-create-btn');
if (prompt) prompt.style.display = 'none';
if (createBtn) createBtn.style.display = 'none';
// Whirlpool spinner while the POST is in flight.
let _wp = null;
if (pending) {View on GitHub (pinned to f9235ebbf1)
Solutions
- Treat 404 as success-or-idempotent: the token is already gone, so refresh the list instead of showing an error.
- Parse the response body (d.detail) like the PATCH handlers do, and include r.status in the fallback message.
- Disable the Revoke button while the DELETE is in flight to prevent double-fire.
- For 401, redirect to login and let the user retry after re-auth.
Example fix
// before
const r = await fetch(`/api/tokens/${tokenId}`, { method: 'DELETE', credentials: 'same-origin' });
if (!r.ok) throw new Error('Revoke failed');
// after
const r = await fetch(`/api/tokens/${tokenId}`, { method: 'DELETE', credentials: 'same-origin' });
const d = await r.json().catch(() => ({}));
if (r.status === 404) { /* already revoked */ }
else if (!r.ok) throw new Error(d.detail || `Revoke failed (HTTP ${r.status})`); Defensive patterns
Strategy: try-catch
Validate before calling
if (!formEl.dataset.createdTokenId) return; // already present; add a re-check right before fetch in case Revoke ran
Try / catch
try { const r = await fetch(`/api/tokens/${tokenId}`, { method: 'DELETE', credentials: 'same-origin' }); const d = await r.json().catch(() => ({})); if (r.status === 404) { /* already revoked */ } else if (!r.ok) throw new Error(d.detail || `Revoke failed (HTTP ${r.status})`); } catch (err) { msg.textContent = err?.message || 'Revoke failed'; } Prevention
- Treat DELETE 404 as idempotent success for revoke-style operations.
- Disable the confirm/Revoke button while the DELETE is in flight.
- Parse the error body for detail like the PATCH handlers do.
- Invalidate createdTokenId immediately after a successful revoke.
When it happens
Trigger: Clicking Revoke after confirming the styled dialog: DELETE /api/tokens/{formEl.dataset.createdTokenId}. Produces 404 if the token was already revoked (double-click, or revoked elsewhere), 401/403 if the session expired or the user lacks rights, 500 on server fault.
Common situations: Double-invoking Revoke (button not disabled while the request is in flight); token deleted from another session; auth cookie expired between opening settings and clicking Revoke.
Related errors
- Failed to delete task
- Failed to save signature
- Failed to load SKILL.md
- Failed to load.
- HTTP ${res.status}
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/a08009c815b713e9.
Report an issue: GitHub.