lfnovo/open-notebook · error · RuntimeError

Failed to delete record: {str(e)}

Error message

Failed to delete record: {str(e)}

What it means

repo_delete() calls connection.delete(ensure_record_id(record_id)) and wraps any failure as RuntimeError('Failed to delete record: <cause>'). Typical causes: malformed record id string, the record not existing, or connection/permission errors. The original exception is logged via logger.exception before re-raising.

Source

Thrown at open_notebook/database/repository.py:216

        query = "UPDATE $target MERGE $data;"
        # logger.debug(f"Update query: {query}")
        result = await repo_query(query, {"target": record_id, "data": data})
        # if isinstance(result, list):
        #     return [_return_data(item) for item in result]
        return parse_record_ids(result)
    except Exception as e:
        raise RuntimeError(f"Failed to update record: {str(e)}")


async def repo_delete(record_id: Union[str, RecordID]):
    """Delete a record by record id"""

    try:
        async with db_connection() as connection:
            return await connection.delete(ensure_record_id(record_id))
    except Exception as e:
        logger.exception(e)
        raise RuntimeError(f"Failed to delete record: {str(e)}")


async def repo_insert(
    table: str, data: List[Dict[str, Any]], ignore_duplicates: bool = False
) -> List[Dict[str, Any]]:
    """Create a new record in the specified table"""
    try:
        async with db_connection() as connection:
            result = parse_record_ids(await connection.insert(table, data))
            # SurrealDB may return a string error message instead of the expected records
            if isinstance(result, str):
                raise RuntimeError(result)
            return result
    except RuntimeError as e:
        if ignore_duplicates and "already contains" in str(e):
            return []
        # Log transaction conflicts at debug level (they are expected during concurrent operations)
        error_str = str(e).lower()

View on GitHub (pinned to a7de90d38a)

Solutions

  1. Inspect the appended cause and the logged stack trace
  2. Validate the id with ensure_record_id semantics before calling: must be 'table:id' format
  3. Make delete idempotent: check existence first or tolerate 'not found' in the caller
  4. Prevent duplicate delete requests in the UI (disable button after click)

Example fix

// before
await repo_delete(note_id_or_none)
// after
from surrealdb import RecordID
table, _, rid = str(note_id).partition(':')
assert table and rid, 'expected table:id'
await repo_delete(RecordID(table, rid))
Defensive patterns

Strategy: try-catch

Validate before calling

from open_notebook.database.repository import ensure_record_id
try:
    rid = ensure_record_id(record_id)  # raises on malformed id
except Exception:
    raise InvalidInputError('expected table:id record id')

Type guard

import re
def is_record_id(v) -> bool:
    return isinstance(v, str) and bool(re.match(r'^[a-zA-Z_][a-zA-Z0-9_]*:[^:]+$', v)) or isinstance(v, RecordID)

Try / catch

try:
    await repo_delete(record_id)
except RuntimeError as e:
    if 'not found' in str(e).lower():
        return  # idempotent delete
    raise

Prevention

When it happens

Trigger: Passing a plain string like 'note' or 'note:' instead of a valid 'table:id' record id; deleting an already-deleted record (some SurrealDB versions error on missing targets); connection dropped mid-delete; deleting across a namespace where the table doesn't exist.

Common situations: Double-delete from UI double-clicks or concurrent requests; frontend sending an unsanitized id; retries after a first successful delete; env pointing at the wrong namespace so nothing matches.

Related errors


AI-assisted analysis of lfnovo/open-notebook@a7de90d38a (2026-08-27). Data as JSON: /api/errors/d1fad036bac39836. Report an issue: GitHub.