{"record":{"id":"764104730b262d9f","repo":"lfnovo/open-notebook","slug":"cannot-delete-object-without-an-id","errorCode":null,"errorMessage":"Cannot delete object without an ID","messagePattern":"Cannot delete object without an ID","errorType":"validation","errorClass":"InvalidInputError","httpStatus":400,"severity":"error","filePath":"open_notebook/domain/base.py","lineNumber":207,"sourceCode":"            raise\n        except RuntimeError:\n            # Transaction conflicts should propagate for retry\n            raise\n        except Exception as e:\n            logger.error(f\"Error saving record: {e}\")\n            raise DatabaseOperationError(e)\n\n    def _prepare_save_data(self) -> Dict[str, Any]:\n        data = self.model_dump()\n        return {\n            key: value\n            for key, value in data.items()\n            if value is not None or key in self.__class__.nullable_fields\n        }\n\n    async def delete(self) -> bool:\n        if self.id is None:\n            raise InvalidInputError(\"Cannot delete object without an ID\")\n        try:\n            logger.debug(f\"Deleting record with id {self.id}\")\n            return await repo_delete(self.id)\n        except Exception as e:\n            logger.error(\n                f\"Error deleting {self.__class__.table_name} with id {self.id}: {str(e)}\"\n            )\n            raise DatabaseOperationError(\n                f\"Failed to delete {self.__class__.table_name}\"\n            )\n\n    async def relate(\n        self, relationship: str, target_id: str, data: Optional[Dict] = {}\n    ) -> Any:\n        if not relationship or not target_id or not self.id:\n            raise InvalidInputError(\"Relationship and target ID must be provided\")\n        try:\n            return await repo_relate(","sourceCodeStart":189,"sourceCodeEnd":225,"githubUrl":"https://github.com/lfnovo/open-notebook/blob/a7de90d38aaf18ee85fd661854d35c11e44613e2/open_notebook/domain/base.py#L189-L225","documentation":"delete() refuses to operate on a model instance whose id is None, raising InvalidInputError. An unsaved (never persisted) object has no SurrealDB record id, so there is nothing to delete — the guard prevents meaningless repo_delete calls.","triggerScenarios":"obj = Note(...); await obj.delete() before ever calling save(); re-instantiating a model from partial data (no id field) and calling delete; deleting after a failed save left id unset.","commonSituations":"Delete-immediately-after-create flows where save failed silently; frontend delete button firing before the object finished persisting; test fixtures building models without ids.","solutions":["Ensure save() succeeded and the instance has an id before calling delete()","Guard with: if obj.id is None: skip or raise a 404-style result","If delete-after-failed-save, handle the save error first rather than proceeding"],"exampleFix":"# before\nnote = Note(title=\"x\")\nawait note.save()  # may have failed\nawait note.delete()\n\n# after\nnote = Note(title=\"x\")\nawait note.save()\nif note.id:\n    await note.delete()","handlingStrategy":"type-guard","validationCode":"if note.id is None:\n    # nothing persisted yet; nothing to delete\n    return","typeGuard":"from open_notebook.domain.base import ObjectModel\n\ndef is_persisted(obj: ObjectModel) -> bool:\n    return obj.id is not None","tryCatchPattern":null,"preventionTips":["Await and check save() succeeded before exposing delete in the UI","Guard delete handlers with `if obj.id is None: return`","In tests, always create objects via the fixture that persists them"],"tags":["validation","delete","unsaved-object","open-notebook"],"backgroundTag":"operation-on-unsaved-entity","analyzedSha":"a7de90d38aaf18ee85fd661854d35c11e44613e2","analyzedAt":"2026-08-27T02:39:58.166Z","schemaVersion":2},"datasetVersion":"2026-08-27T03:17:27.898Z"}