lfnovo/open-notebook · error · RuntimeError

Failed to create record

Error message

Failed to create record

What it means

repo_create() wraps record creation; RuntimeError escapes with its original message (logged), while any other exception is masked as a generic RuntimeError('Failed to create record') with the real cause only in logs. The underlying failure is almost always a SurrealQL/schema issue: missing table, schema validation, duplicate unique key, or serialization of an unsupported field type.

Source

Thrown at open_notebook/database/repository.py:139

async def repo_create(table: str, data: Dict[str, Any]) -> Dict[str, Any]:
    """Create a new record in the specified table"""
    # Remove 'id' attribute if it exists in data
    data.pop("id", None)
    data["created"] = datetime.now(timezone.utc)
    data["updated"] = datetime.now(timezone.utc)
    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 record
            if isinstance(result, str):
                raise RuntimeError(result)
            return result
    except RuntimeError as e:
        logger.error(str(e))
        raise
    except Exception as e:
        logger.exception(e)
        raise RuntimeError("Failed to create record")


async def repo_relate(
    source: Union[str, RecordID],
    relationship: str,
    target: Union[str, RecordID],
    data: Optional[Dict[str, Any]] = None,
) -> List[Dict[str, Any]]:
    """Create a relationship between two records with optional data"""
    if data is None:
        data = {}
    # relationship is an edge-table name, not a record value, so it can't be
    # bound as a query parameter; validate it against an identifier allowlist
    # instead of trusting the caller. source/target are always bound.
    _ensure_safe_identifier(relationship, "relationship")
    query = f"RELATE $source->{relationship}->$target CONTENT $data;"
    # logger.debug(f"Relate query: {query}")

View on GitHub (pinned to a7de90d38a)

Solutions

  1. Check the API/worker logs — logger.exception printed the real underlying exception just before this re-raise
  2. Confirm schema migrations ran (restart make api and read startup logs)
  3. Verify the target table exists in the expected namespace/database and the payload uses JSON-primitive values (isoformat dates, str ids)
  4. If a unique index exists, check for a duplicate record before create
Defensive patterns

Strategy: try-catch

Validate before calling

rows = await repo_query(f"SELECT id FROM {table} LIMIT 1")
if not rows:
    logger.warning('table %s may not exist yet', table)

Try / catch

try:
    rec = await repo_create(table, data)
except RuntimeError as e:
    logger.exception('create failed for %s', table)
    raise HTTPException(500, 'Failed to create record') from e

Prevention

When it happens

Trigger: Creating a record for a table that doesn't exist yet (migrations not run); inserting a field value SurrealDB can't store (e.g. datetime without isoformat, custom objects); violating a unique index; record data containing RecordID strings in a field defined with a different type in schema.

Common situations: API started against a fresh database before migrations; schema drift after model changes (new required field not in DB schema); passing enums or Python objects instead of primitives; namespace/database mismatch so the table lives elsewhere.

Related errors


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