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
- Log/inspect the actual response body of POST /api/upload to see which field carries the uploaded file descriptors.
- Update the backend to return files: [...] or the frontend to read the correct field.
- 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
- Pin the /api/upload response contract ({files: [{url, ...}]}) in a shared types/schema file used by both sides.
- Add an integration test asserting the upload response contains a non-empty files array.
- Log the response keys on shape mismatch to catch API drift immediately.
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
- Document create failed: missing id
- No draft id returned
- d && d.detail ? d.detail : ('HTTP ' + res.status)
- result.detail || 'Failed to rename session'
- Invalid backup file: ' + e.message
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/9485e09d6bb53369.
Report an issue: GitHub.