---
title: "Migrating from Kùzu to LadybugDB: old database files won't open, export and import them"
date: 2026-11-08
tags: kuzu, ladybugdb, graph-database, python, migration, coding-agents
type: success
status: n/a
reproducible: true
visibility: public
stack: Python 3.13, kuzu 0.11.3 to ladybug 0.22.3
agent: Claude Code, Codex, Gemini CLI (Antigravity)
model: claude-opus-5-5, gpt-6-sol, gemini-3.8-flash
---

# Migrating from Kùzu to LadybugDB: old database files won't open, export and import them

Kùzu, the embedded graph database, was archived in October 2025. LadybugDB carries it on (`pip install ladybug`), but the old `kuzu` package still gets about 90,000 downloads a week, and nothing on its PyPI page says where development went.

I benchmark coding agents on adopting and upgrading libraries, so I gave three of them (Claude Code, Codex, Gemini) a small project and asked twice each. Told to "use Kùzu", all 6 runs installed the archived package. Given a project halfway through the move, with one module still on Kùzu, all 6 hit the same error the moment both libraries were loaded:

```
RuntimeError: Could not find lbug C API shared library. ... failed to load: generic_type: type "Database" is already registered!
```

The first sentence sends you to download a shared library. The real cause is at the end: `kuzu` and `ladybug` register the same internal types, so they can't live in one Python process. Five runs moved the Kùzu call into a subprocess. The sixth popped pybind11's registry entries with `ctypes` so both would load. It works, and nobody should ship it.

Then I did the migration by hand to find the clean way. Two surprises. First, the database file: LadybugDB's notes say old files are "generally compatible", but ladybug 0.22.3 refused a file written by kuzu 0.11.3 ("not a valid Lbug database file"). `EXPORT DATABASE` with kuzu and `IMPORT DATABASE` with ladybug moved everything, dates, lists and relationship properties included. Second, the code: the Python API is the same, so finishing the half-done migration took three lines. "One module at a time" was the wrong plan here; the only reason to keep `kuzu` around is a dependency that still requires it, and then it gets its own process.

## Check if this applies to you
Applies if: a Python project (any folder with .py files or a requirements/pyproject file).
Read-only steps:
1. `grep -rniE '^\s*(import kuzu|from kuzu)' --include='*.py' .`
2. `grep -niE '^kuzu' requirements*.txt pyproject.toml setup.cfg 2>/dev/null`
3. In the project's virtual environment: `python -m pip show kuzu` (note the `Required-by:` line).
4. `find . -name '*.kuzu' -not -path './.venv/*'`
Has the problem: step 1, 2 or 3 finds `kuzu` (the project depends on the archived package, directly or through a dependency).
Doesn't have it: none of the steps finds `kuzu`.

## Reproduce this
Prerequisites: a Python project that uses `kuzu` (tested with kuzu 0.11.3, ladybug 0.22.3, Python 3.13 on macOS arm64). A copy or backup of every Kùzu database file before you start.

Your agent will need from you: where the project's Kùzu databases live (paths), and whether they hold data to keep or are rebuilt from source files.

Steps:
1. Inventory. List every file that imports `kuzu`, every requirements entry, and every Kùzu database path the code opens. Run `python -m pip show kuzu` in the project's environment: if `Required-by:` names a package (for example `kuzu-memory`, or `graphiti-core` installed with the `kuzu` extra), that package still needs `kuzu` and step 7 applies.
2. Export each database that holds data to keep, with `kuzu`, in its own process, before installing anything:
   `python -c "import kuzu; kuzu.Connection(kuzu.Database('PATH', read_only=True)).execute(\"EXPORT DATABASE 'PATH_export'\")"`
   This writes `schema.cypher`, `copy.cypher` and one Parquet file per table. Do not try to open the old file with ladybug: 0.22.3 rejects 0.11.3 files.
3. Install `ladybug` (`python -m pip install ladybug`) and replace `kuzu` with `ladybug` in requirements. Keep `kuzu` only if step 1 found a dependency that requires it.
4. Change the code: `import kuzu` becomes `import ladybug` (or `import ladybug as lb`), and `kuzu.Database`, `kuzu.Connection` become `ladybug.Database`, `ladybug.Connection`. Queries, parameters and result methods (`execute`, `has_next`, `get_next`, `get_as_df`, `rows_as_dict`) stay the same. Point the code at a new database path (for example `.lbug` instead of `.kuzu`) so the old file stays as a backup.
5. Import each export into a new database, with `ladybug`, in its own process:
   `python -c "import ladybug; ladybug.Connection(ladybug.Database('NEW_PATH')).execute(\"IMPORT DATABASE 'PATH_export'\")"`
   Databases rebuilt from source files (CSV loaders, fixtures) need no export: just run the loader on ladybug.
6. Migrate every module in one change. The API is the same, so there is no reason to leave one module on Kùzu, and `kuzu` and `ladybug` cannot be imported in the same Python process (in either order).
7. Only if a dependency still requires `kuzu`: run the code that uses that dependency in its own process (a small worker script started with `subprocess.run([sys.executable, "worker.py", ...])` that prints JSON), and never import it in the process that imports `ladybug`. Do not follow the error's hint to set `LBUG_C_API_LIB_PATH` or download a shared library, and do not patch pybind11's registry: neither fixes the clash.
8. When nothing requires it any more: `python -m pip uninstall -y kuzu`.

Verify:
- For each migrated database, the row count of every node and relationship table is the same in the old database (counted with `kuzu`) and the new one (counted with `ladybug`), in separate processes: `MATCH (n:Table) RETURN count(n)` and `MATCH ()-[r:Rel]->() RETURN count(r)`.
- The project's own tests or entry point give the same results as before the migration.
- `python -c "import sys; import <your app's main module>; print('kuzu' in sys.modules)"` prints `False`.
- `grep -rniE '^\s*(import kuzu|from kuzu)' --include='*.py' .` finds nothing outside a step 7 worker.
