lfnovo/open-notebook · error · InvalidInputError
Cannot delete object without an ID
Error message
Cannot delete object without an ID
What it means
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.
Source
Thrown at open_notebook/domain/base.py:207
raise
except RuntimeError:
# Transaction conflicts should propagate for retry
raise
except Exception as e:
logger.error(f"Error saving record: {e}")
raise DatabaseOperationError(e)
def _prepare_save_data(self) -> Dict[str, Any]:
data = self.model_dump()
return {
key: value
for key, value in data.items()
if value is not None or key in self.__class__.nullable_fields
}
async def delete(self) -> bool:
if self.id is None:
raise InvalidInputError("Cannot delete object without an ID")
try:
logger.debug(f"Deleting record with id {self.id}")
return await repo_delete(self.id)
except Exception as e:
logger.error(
f"Error deleting {self.__class__.table_name} with id {self.id}: {str(e)}"
)
raise DatabaseOperationError(
f"Failed to delete {self.__class__.table_name}"
)
async def relate(
self, relationship: str, target_id: str, data: Optional[Dict] = {}
) -> Any:
if not relationship or not target_id or not self.id:
raise InvalidInputError("Relationship and target ID must be provided")
try:
return await repo_relate(View on GitHub (pinned to a7de90d38a)
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
Example fix
# before
note = Note(title="x")
await note.save() # may have failed
await note.delete()
# after
note = Note(title="x")
await note.save()
if note.id:
await note.delete() Defensive patterns
Strategy: type-guard
Validate before calling
if note.id is None:
# nothing persisted yet; nothing to delete
return Type guard
from open_notebook.domain.base import ObjectModel
def is_persisted(obj: ObjectModel) -> bool:
return obj.id is not None Prevention
- 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
When it happens
Trigger: 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.
Common situations: Delete-immediately-after-create flows where save failed silently; frontend delete button firing before the object finished persisting; test fixtures building models without ids.
Related errors
- ID cannot be empty
- Failed to delete {self.__class__.table_name}
- Error deleting session: {str(e)}
- Failed to check environment status
- Failed to delete credential
AI-assisted analysis of lfnovo/open-notebook@a7de90d38a (2026-08-27).
Data as JSON: /api/errors/764104730b262d9f.
Report an issue: GitHub.