odysseus-dev/odysseus · warning · Error

Server error ' + res.status

Error message

Server error ' + res.status

What it means

HTTP 404 from PUT /{file_id}/vision when the caller is authenticated but neither the file's owner nor an admin — the owner-or-admin gate on the write path. Returned as 404 to avoid leaking other users' files. It is the second 404 on this route: the first (index miss) fires earlier, so reaching this one means the file exists in uploads.json but belongs to someone else.

Solutions

  1. Save the OCR edit from the owning account (or an admin) — the UI should only expose the editor to the owner.
  2. Inspect uploads.json to confirm the owner if access should have worked; repair misattributed ownership after user renames/migrations.
  3. Return a clear 'read-only' state in the UI for files the current user doesn't own to prevent the failed save.
Defensive patterns

Strategy: validation

Validate before calling

const canEdit = (att, user) => !authConfigured || att.owner === user || user?.isAdmin;
if (!canEdit(att, currentUser)) renderReadOnly(att);

Try / catch

const r = await fetch(url, { method: 'PUT', credentials: 'include' });
if (r.status === 404) showNotEditable(att); // not owner — hide editor

Prevention

When it happens

Trigger: PUT /api/upload/{id}/vision as user B for a file whose index entry has owner=user A and B is not admin; ownership field changed by a migration so the editor no longer matches.

Common situations: Shared workstation/browser profile logged in as a different account; admin edited the file earlier, then a non-admin account tries to save over that edit.

Related errors


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

Appendix: source

Thrown at static/js/chat.js:6266

      }
    }

    if (!msgIds.length || !sessionId) {
      // No persisted rows to delete (no DB IDs, or no session at all — e.g. an
      // error output shown before a model was selected, #1428). Just remove the
      // DOM so the "x" works regardless.
      domToRemove.forEach(el => el.remove());
      if (uiModule) uiModule.showToast('Message deleted');
      return;
    }

    try {
      const res = await fetch(`${API_BASE}/api/session/${sessionId}/delete-messages`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ msg_ids: msgIds })
      });
      if (!res.ok) throw new Error('Server error ' + res.status);
      domToRemove.forEach(el => el.remove());
      if (uiModule) uiModule.showToast('Message deleted');
    } catch (err) {
      console.error('Delete failed:', err);
      if (uiModule) uiModule.showError('Delete failed: ' + err.message);
    }
  }

  /**
   * Edit an AI message inline. Makes the body contentEditable, saves to DB on confirm.
   */
  export async function editAIMessage(msgElement) {
    const body = msgElement.querySelector('.body');
    if (!body) return;

    const isEditing = body.contentEditable === 'true' || body.contentEditable === 'plaintext-only';
    if (isEditing) return; // already editing

View on GitHub (pinned to f9235ebbf1)