odysseus-dev/odysseus · error · Error

No uploaded files returned

Error message

No uploaded files returned

What it means

Thrown in static/js/document.js after a successful POST /api/upload when the parsed JSON has no non-empty files array (data.files is missing or empty). It is a contract violation: the upload endpoint returned success but not the expected {files: [...]} shape, so there is nothing to insert into the markdown.

Source

Thrown at static/js/document.js:3573

    }
    if (_activeDocLanguage() !== 'markdown') {
      if (uiModule) uiModule.showError('Switch the document to markdown before inserting images');
      return;
    }

    const fd = new FormData();
    images.forEach(file => fd.append('files', file));
    try {
      const res = await fetch(`${API_BASE}/api/upload`, {
        method: 'POST',
        credentials: 'same-origin',
        body: fd,
      });
      let data = null;
      try { data = await res.json(); } catch (_) {}
      if (!res.ok) throw new Error((data && (data.error || data.detail)) || `HTTP ${res.status}`);
      const uploaded = Array.isArray(data?.files) ? data.files : [];
      if (!uploaded.length) throw new Error('No uploaded files returned');
      _insertMarkdownImages(uploaded);
      if (uiModule) uiModule.showToast(images.length === 1 ? 'Image inserted' : 'Images inserted');
    } catch (err) {
      console.error('Failed to insert markdown image:', err);
      if (uiModule) uiModule.showError('Failed to insert image');
    }
  }

  async function _handleMarkdownImageUpload(e) {
    const files = e.target.files;
    e.target.value = '';
    await _uploadMarkdownImages(files);
  }

  function _renderComposeAttachments() {
    const container = document.getElementById('doc-email-compose-atts');
    if (!container) return;
    const doc = docs.get(activeDocId);

View on GitHub (pinned to f9235ebbf1)

Solutions

  1. Log/inspect the actual response body of POST /api/upload to see which field carries the uploaded file descriptors.
  2. Update the backend to return files: [...] or the frontend to read the correct field.
  3. Make the backend return a non-2xx status when zero files were stored instead of an empty 200.

Example fix

// before
const uploaded = Array.isArray(data?.files) ? data.files : [];
if (!uploaded.length) throw new Error('No uploaded files returned');

// after — accept the documented aliases, fail loudly otherwise
const uploaded = Array.isArray(data?.files) ? data.files
  : Array.isArray(data?.uploaded) ? data.uploaded : [];
if (!uploaded.length) throw new Error(`Upload returned no files (payload keys: ${Object.keys(data || {}).join(', ')})`);
Defensive patterns

Strategy: validation

Validate before calling

if (!images.length) return; // don't upload nothing and expect files back

Type guard

function hasUploadedFiles(data) {
  return data != null && typeof data === 'object'
    && Array.isArray(data.files) && data.files.length > 0
    && data.files.every(f => f && (f.url || f.path));
}

Try / catch

if (!hasUploadedFiles(data)) {
  console.error('Unexpected upload response shape:', data);
  if (uiModule) uiModule.showError('Upload succeeded but returned no files');
  return;
}

Prevention

When it happens

Trigger: Backend version that returns {urls: [...]} or {uploaded: [...]} instead of files; endpoint responding with {success: true} but zero saved files (e.g. all files filtered server-side); response actually an empty object because a proxy stripped the body.

Common situations: Frontend/backend drift after an API refactor renamed the response field; backend silently dropping files that fail validation while still returning 200.

Related errors


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