odysseus-dev/odysseus · warning · Error

await res.text()

Error message

await res.text()

What it means

HTTP 400 from PUT /{file_id}/vision when the file_id path parameter fails upload_handler.validate_upload_id() — the same ^[0-9a-fA-F]{32}(\.[A-Za-z0-9]+)?$ gate used across the upload routes. On this write route it runs before the index lookup and before any auth check, so a malformed id is always reported as 400 regardless of credentials.

Source

Thrown at static/js/chat.js:241

    const pill = document.getElementById('chat-context-pill');
    if (!pill) return;
    pill.addEventListener('click', (e) => {
      e.preventDefault();
      e.stopPropagation();
      _showContextHeaderPopup();
    });
  }

  export async function compactCurrentChatContext() {
    const sm = _liveSessionModule();
    const sid = sm && sm.getCurrentSessionId && sm.getCurrentSessionId();
    if (!sid) {
      uiModule.showToast('Open a chat first');
      return false;
    }
    try {
      const res = await fetch(`/api/session/${encodeURIComponent(sid)}/compact`, { method: 'POST' });
      if (!res.ok) throw new Error(await res.text());
      uiModule.showToast('Context compacted');
      _closeContextHeaderPopup();
      if (sm && sm.selectSession) await sm.selectSession(sid, { keepSidebar: true, showLoading: false });
      refreshChatContextHeader('compact');
      return true;
    } catch (err) {
      uiModule.showError(`Compact failed: ${err.message || err}`);
      return false;
    }
  }
  try { window.compactCurrentChatContext = compactCurrentChatContext; } catch (_) {}

  export async function refreshChatContextHeader(reason = '') {
    _bindContextHeaderPill();
    const pill = document.getElementById('chat-context-pill');
    if (!pill) return;
    const sm = _liveSessionModule();
    const sid = sm && sm.getCurrentSessionId && sm.getCurrentSessionId();

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Send the upload id from the upload response's `id` field verbatim.
  2. Client-side validate with ^[0-9a-fA-F]{32}(\.[A-Za-z0-9]+)?$ before PUT.
  3. Log the outbound URL when debugging — most cases are a wrong field or truncation.
  4. Keep exactly one extension segment if appending one.
Defensive patterns

Strategy: validation

Validate before calling

const UPLOAD_ID_RE = /^[0-9a-fA-F]{32}(\.[A-Za-z0-9]+)?$/;
if (!UPLOAD_ID_RE.test(fileId)) return saveRejected('bad id');

Type guard

const isUploadId = (v) => typeof v === 'string' && /^[0-9a-fA-F]{32}(\.[A-Za-z0-9]+)?$/.test(v);

Prevention

When it happens

Trigger: PUT /api/upload/{id}/vision with an id that is not 32-char hex (+optional single extension): truncated id, traversal string, or the client sending a filename.

Common situations: Editing OCR text in the UI where the attachment record was built from the original filename; id mutated by an upstream URL builder; stale client code predating the id format.

Related errors


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