{"record":{"id":"5b5ae7e3ab42ef72","repo":"odysseus-dev/odysseus","slug":"referenced-upload-is-no-longer-available-missing-5b5ae7","errorCode":null,"errorMessage":"Referenced upload is no longer available: {missing_id}","messagePattern":"Referenced upload is no longer available: (.+?)","errorType":"http","errorClass":"HTTPException","httpStatus":409,"severity":"error","filePath":"routes/calendar_routes.py","lineNumber":815,"sourceCode":"        for d in results:\n            d[\"truncated\"] = True\n\n    return results\n\n\n# ── Routes ──\n\ndef setup_calendar_routes(upload_handler=None) -> APIRouter:\n    router = APIRouter(prefix=\"/api/calendar\", tags=[\"calendar\"])\n\n    def _reserve_calendar_uploads(request: Request, *values) -> None:\n        missing_id = reserve_upload_references(\n            upload_handler,\n            effective_user(request),\n            *values,\n        )\n        if missing_id:\n            raise HTTPException(409, f\"Referenced upload is no longer available: {missing_id}\")\n\n    # ── CalDAV multi-account helpers ─────────────────────────────────────────\n\n    def _get_caldav_accounts(owner: str) -> list:\n        from src.caldav_sync import _load_caldav_accounts\n        return _load_caldav_accounts(owner)\n\n    def _save_caldav_accounts(owner: str, accounts: list) -> None:\n        from routes.prefs_routes import _load_for_user, _save_for_user\n        prefs = _load_for_user(owner) or {}\n        prefs[\"caldav_accounts\"] = accounts\n        prefs.pop(\"caldav\", None)\n        _save_for_user(owner, prefs)\n\n    # ── CalDAV config routes (backward-compat single-account API) ────────────\n\n    @router.get(\"/config\")\n    async def get_config(request: Request):","sourceCodeStart":797,"sourceCodeEnd":833,"githubUrl":"https://github.com/odysseus-dev/odysseus/blob/f9235ebbf13f693a6fd29ce70b097f6ec83705bf/routes/calendar_routes.py#L797-L833","documentation":"HTTP 409 raised when _reserve_calendar_references finds an upload id referenced by the request payload that cannot be reserved for the caller: reserve_upload_references extracts internal upload ids (URLs, PDF source refs, attachment lines) from the payload values and owner-checks them against the upload handler. A missing id means the referenced upload was deleted, expired, or belongs to another user, so the calendar write is rejected before persisting dangling references.","triggerScenarios":"Creating/updating a calendar event whose description or attachment field references /uploads/... or an attachment line for a file that no longer exists or is owned by another account. The 409 body names the first offending id.","commonSituations":"Copy-pasting event content with an old upload URL between users or instances; upload expired/cleaned by retention policy; multi-user deployment where uploads are owner-scoped.","solutions":["Remove or re-upload the attachment named in the 409 message, then retry.","If the upload should exist, verify it belongs to the same owner and was not cleaned up.","When copying events across users/instances, strip internal upload references or re-attach the files under the target owner."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const UPLOAD_RE = /[internal upload url pattern]/; // extract ids like the server does\nfunction referencedUploads(text) { return [...text.matchAll(UPLOAD_RE)].map(m => m[1]); }\n// verify each id still exists and is owned by the caller before POSTing the event","typeGuard":null,"tryCatchPattern":"catch (e) {\n  if (e.status === 409) { const id = e.message.split(': ').pop(); stripReference(id); retry(); }\n  else throw e;\n}","preventionTips":["Only attach uploads created by the same account in the same instance.","When copying event content across users, strip internal upload URLs first.","Handle 409 by removing the named reference rather than re-uploading blindly."],"tags":["conflict","http-409","uploads","calendar","references","ownership"],"backgroundTag":null,"analyzedSha":"f9235ebbf13f693a6fd29ce70b097f6ec83705bf","analyzedAt":"2026-08-14T21:47:48.359Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}