odysseus-dev/odysseus · error · HTTPException
Referenced upload is no longer available: {missing_id}
Error message
Referenced upload is no longer available: {missing_id} What it means
409 raised by _reserve_note_uploads (routes/note/note_routes.py:603) during note create/update when the note body references an upload id that reserve_upload_references reports as missing — i.e. the uploaded file was deleted or expired before the note was saved. It is an integrity guard: it prevents persisting a note pointing at a dead upload.
Source
Thrown at routes/note/note_routes.py:603
router = APIRouter(prefix="/api/notes", tags=["notes"])
def _owner(request: Request) -> Optional[str]:
# require_user, not bare get_current_user: a request that reaches
# these owner-scoped routes with NO identity (auth-middleware
# regression, SSRF from a sibling service) must fail closed (401)
# when auth is configured — not be treated as the single-user mode
# and handed blanket access to every account's notes. The documented
# anonymous modes (AUTH_ENABLED=false, LOCALHOST_BYPASS on loopback,
# unconfigured first-run) still resolve to None, the single-user
# path. fire_reminder below already gated this way; the CRUD routes
# did not.
return require_user(request) or None
def _reserve_note_uploads(owner: Optional[str], *values) -> None:
missing_id = reserve_upload_references(upload_handler, owner, *values)
if missing_id:
raise HTTPException(409, f"Referenced upload is no longer available: {missing_id}")
def _is_admin_or_single_user(request: Request, user: str | None) -> bool:
if user == INTERNAL_TOOL_USER:
return True
if not user:
# require_user() already admitted this request, which only happens
# for auth-disabled, loopback-bypass, or unconfigured single-user
# modes. There is no separate non-admin account boundary there.
return True
try:
from core.auth import AuthManager
auth_mgr = getattr(request.app.state, "auth_manager", None) or AuthManager()
if not getattr(auth_mgr, "is_configured", True):
return True
return bool(auth_mgr.is_admin(user))
except Exception:
return False
View on GitHub (pinned to f9235ebbf1)
Solutions
- Re-upload the file (or re-select a fresh upload) and save the note with the new upload id.
- If the attachment is optional, strip the dead reference from image_url/content/items and save without it.
- Check upload lifetime/retention config; raise the TTL if drafts regularly outlive uploads.
- For the 409 path specifically, the message names the missing upload id — use it to find which field referenced it.
Example fix
# before
note = {"title": t, "image_url": f"/uploads/{OLD_ID}"} # OLD_ID purged -> 409
client.put(f"/notes/{id}", json=note)
# after
if not upload_exists(OLD_ID):
new_id = reupload_file(path)
note["image_url"] = f"/uploads/{new_id}"
client.put(f"/notes/{id}", json=note) Defensive patterns
Strategy: validation
Validate before calling
for (const id of extractUploadIds(note)) {
if (!await api.uploadExists(id)) throw new Error(`upload ${id} gone; re-upload before saving`);
} Try / catch
try { await api.updateNote(id, body); }
catch (e) {
if (e.status === 409 && /upload/i.test(e.message)) { await reuploadMissing(e.missingId); await api.updateNote(id, body); }
else throw e;
} Prevention
- Save notes soon after attaching uploads; keep upload TTL well above draft lifetimes.
- Strip dead upload references before saving rather than forcing them through.
- Log the missing id from the 409 message to locate the offending field.
When it happens
Trigger: Attaching an upload to a note, letting the upload expire/get garbage-collected (or deleting it via the uploads UI), then saving the note. Drafting a note with an image, upload TTL elapses, hitting save. Replaying an old create/update payload whose upload id was since purged.
Common situations: Upload retention windows shorter than typical note-draft lifetimes. Cleanup jobs removing orphaned uploads while a draft still references them. Client caching an upload id across sessions after server-side purge.
Related errors
- Referenced upload is no longer available: {missing_upload_id
- Username already taken
- Referenced upload is no longer available: {missing_id}
- Source PDF {upload_id} not found in uploads
- Source PDF {upload_id} not found
AI-assisted analysis of odysseus-dev/odysseus@f9235ebbf1 (2026-08-14).
Data as JSON: /api/errors/61515e14eba627b9.
Report an issue: GitHub.