DuckDB hit an internal assertion while reading the metadata block chain of your .duckdb file. In practice this almost always means the file is corrupt, usually because a previous write, checkpoint, or WAL replay was interrupted before it finished.
The error fires the moment you connect to a persisted database file. The id, idx, and ptr numbers change per file, but the message shape is stable across clients.
InternalException: INTERNAL Error: Failed to load metadata pointer (id 189, idx 48, ptr 3458764513820541117)
This error signals an assertion failure within DuckDB. This usually occurs due to unexpected conditions or errors in the program's logic.
For more information, see https://duckdb.org/docs/dev/internal_errors
# Go client variant (via go-duckdb):
duckdb error: INTERNAL Error: Failed to load metadata pointer (id 4950, idx 61, ptr 4395513236313609046)
# CLI variant:
Error: INTERNAL Error: Failed to load metadata pointer (id 12, idx 3, ptr 0)duckdb failed to load metadata pointer, Failed to load the metadata pointer, INTERNAL Error: Failed to load metadata pointer, InternalException Failed to load metadata pointerRanked most-likely first.
A container hit its memory cgroup and got OOM-killed, someone ran kill -9, the machine lost power, or the filesystem was force-remounted. DuckDB's on-disk metadata is a linked chain of blocks, and a half-written checkpoint leaves a pointer that references a block that was never flushed. The next open reads that pointer and asserts.
This is DuckDB issue #9667 and the pattern is well-known. A large INSERT or CHECKPOINT fills the disk, the write returns short, DuckDB proceeds anyway, and the file is now inconsistent. The failure shows up on the next open, not the write that caused it.
cp, rsync, docker cp, or a scheduled backup that grabbed the .duckdb file mid-transaction produces a torn snapshot. The metadata blocks in the copy point at data blocks that were not yet flushed. This is not a DuckDB bug, it is how any read-uncoordinated snapshot of an active database file behaves.
Several DuckDB releases across 0.9, 0.10, and early 1.x lines had bugs where WAL replay or checkpoint could produce an invalid metadata pointer under specific workloads. If the file was written by an old version, upgrading and reopening may or may not read past the damage, but it stops the corruption from recurring.
On open, DuckDB replays the write-ahead log into the main file before it lets you query. If either the main file's metadata chain or the WAL itself is inconsistent, replay can surface the error even when the base file alone might be readable.
None of the recovery steps below are guaranteed. Some can make things worse. Copy the database file and its .wal sibling somewhere read-only before you experiment, so you always have the exact bytes to hand to a support channel or a later attempt.
cp -a my_database.duckdb /tmp/rescue/my_database.duckdb.bak
cp -a my_database.duckdb.wal /tmp/rescue/my_database.duckdb.wal.bak 2>/dev/null || trueIf the WAL replay is the part that trips the assertion, opening the base file alone gives you the last checkpointed state. You lose any transactions that were only in the WAL, but you may get a working database. Move the .wal aside, do not delete it.
mv my_database.duckdb.wal my_database.duckdb.wal.aside
duckdb my_database.duckdb 'SELECT 42;'If the file was written by an older release with a fixed storage bug, a current DuckDB may be able to read past the damage. Install the latest stable release and try the connect again. This is also worth trying in read-only mode.
# CLI, latest release:
duckdb -readonly my_database.duckdb
# Python:
# pip install --upgrade duckdb
# con = duckdb.connect('my_database.duckdb', read_only=True)Assume the connection is fragile. Get the data out into a portable format before doing anything else. EXPORT DATABASE writes one Parquet file per table plus a schema.sql, and IMPORT DATABASE rebuilds a fresh, clean file from it.
EXPORT DATABASE '/tmp/rescue/export' (FORMAT PARQUET);
-- Then, in a fresh DuckDB session:
ATTACH '/tmp/rescue/clean.duckdb' AS clean;
USE clean;
IMPORT DATABASE '/tmp/rescue/export';The corruption may only affect one region of the file. Query information_schema for the table list, then COPY each one to Parquet individually. Any table that errors is one you skip and reingest from source; the rest you keep.
SELECT table_name FROM information_schema.tables WHERE table_schema = 'main';
-- For each surviving table:
COPY my_table TO '/tmp/rescue/my_table.parquet' (FORMAT PARQUET);DuckDB does not ship a repair tool for metadata-chain corruption. If steps 2 through 5 all fail, the file is not recoverable through DuckDB itself. Restore from your last known-good backup, or rebuild the database from source Parquet, CSV, or upstream tables.
# Rebuild from source data, whatever that looks like:
duckdb fresh.duckdb <<'SQL'
CREATE TABLE events AS SELECT * FROM 's3://my-bucket/events/*.parquet';
SQLNever SIGKILL a DuckDB process holding a file open for writes, and never let the runtime do it either. In Docker and Kubernetes, set DuckDB's memory_limit safely below the container's memory ceiling so the OOM killer never fires. A 12 GB container should run DuckDB with an 8 to 10 GB memory_limit, not 12.
Never copy, rsync, or snapshot a .duckdb file that has a live writer. Close all connections and CHECKPOINT before you copy, or use EXPORT DATABASE for a snapshot-consistent, portable backup that survives version upgrades.
Monitor free space on the volume holding the database and its temp_directory. A write that runs out of disk mid-checkpoint is one of the most reliable ways to corrupt a DuckDB file, and the failure shows up on the next open rather than the write itself.
Stay on a current DuckDB release. Storage-corruption bugs get fixed release-over-release, and sticking on an old point version leaves you exposed to failure modes that no longer exist upstream.
1bench is a native GUI for DuckDB. Inspect queries, connections, and settings without leaving the app. See what's happening before you have to Google it.
Open DuckDB in 1bench