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

  1. Treat 404 as success-or-idempotent: the token is already gone, so refresh the list instead of showing an error.
  2. Parse the response body (d.detail) like the PATCH handlers do, and include r.status in the fallback message.
  3. Disable the Revoke button while the DELETE is in flight to prevent double-fire.
  4. 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

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


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