ChunkHound Index Compactor
Compact a DuckDB database by rebuilding it into a fresh file. The motivating and supported use case is shrinking a bloated ChunkHound index, whose drop-and-recreate HNSW churn (above its 50-row write-batch threshold) leaves large amounts of orphaned-but-counted blocks. The rebuild pipeline is structurally generic and works on other single-schema DuckDB files, but only ChunkHound-shaped inputs are promised: any shape outside that scope is refused at the front gate (see the Not Supported section below) rather than silently dropped or rebuilt with loss.
That bloat comes back as ChunkHound keeps indexing, so compaction is periodic maintenance rather than a one-time cleanup.
⚡ Quick Start
uvx chunkhound-index-compactor path/to/db.duckdb
# writes path/to/db.duckdb.compacted
uvx chunkhound-index-compactor path/to/db.duckdb --replace
# swaps in the compacted copy and keeps the original at path/to/db.duckdb.bak
uvx chunkhound-index-compactor path/to/.chunkhound --replace
# point at the index directory instead of the .db file inside it
uvx chunkhound-index-compactor path/to/db.duckdb --skip-hnsw
# skips rebuilding vector indexes (RAM-flat, smallest output); restore them later
uvx chunkhound-index-compactor restore path/to/db.duckdb.compacted
uvx fetches a throwaway copy each run. Since compaction is recurring maintenance, install it once with uv tool install chunkhound-index-compactor, then drop the uvx prefix and call chunkhound-index-compactor directly:
uv tool install chunkhound-index-compactor
chunkhound-index-compactor path/to/db.duckdb --replace
The source is opened read-only, but an active writer holds the file lock. Close any process writing to the database before running.
🖥️ CLI Usage
$ chunkhound-index-compactor --help
Usage: chunkhound-index-compactor [OPTIONS] COMMAND [ARGS]...
Options:
--version Show the version and exit.
Commands:
compact Compact a DuckDB database by rebuilding it into a fresh file. (default)
restore Rebuild HNSW vector indexes in a --skip-hnsw artifact, in place.
A bare invocation routes to compact, so chunkhound-index-compactor SOURCE still works:
chunkhound-index-compactor SOURCE [TARGET] [--replace] [--skip-hnsw]
chunkhound-index-compactor restore DATABASE
| Argument / Option | Meaning |
|---|---|
SOURCE |
Path to a DuckDB file, or a ChunkHound index directory (required) |
TARGET |
Path for the compacted output [default: <source>.compacted] |
--replace |
After success, replace source with the compacted file (original → <source>.bak) |
--skip-hnsw |
Do not rebuild vector indexes; write a recipe table for later restore |
--version |
Print the installed package version and exit (top-level flag) |
When SOURCE is a directory, the tool resolves to the single ChunkHound index inside it, so you can pass the index directory directly. Any other directory shape fails, listing the DuckDB files it found (if any) so you can name the exact path.
With --skip-hnsw, the output has no vector index and falls back to a brute-force scan (correct, just unaccelerated) until you run restore. Rebuilding the HNSW is the memory-dominant step, so --skip-hnsw lets you compact on a small machine and restore on a RAM-capable one. See docs/benchmarks.md for peak-RAM numbers and docs/architecture.md §RAM cost asymmetry for why.
🐍 Library Usage
from pathlib import Path
from chunkhound_index_compactor import compact_database, restore_indexes, replace_with_compacted
result = compact_database(Path("big.duckdb"), Path("small.duckdb"))
print(f"{result.source_size} -> {result.target_size} ({result.delta_pct:+.1f}%)")
# Small-RAM path: skip the vector index, restore it later on a bigger machine.
compact_database(Path("big.duckdb"), Path("small.duckdb"), skip_hnsw=True)
restored = restore_indexes(Path("small.duckdb"))
print(f"restored: {restored.restored}")
# Optional: swap in place with .bak backup
backup = replace_with_compacted(result.source, result.target)
compact_database() raises:
ValueError:targetresolves to the same path assource, the FK graph has a cycle, or the source has a shape refused at the front gate (see the Not Supported section below).FileNotFoundError:sourcedoes not exist.FileExistsError:targetalready exists.RuntimeError: the bundledvssextension binary cannot be located (only reachable if the source contains an HNSW index).
restore_indexes() raises:
FileNotFoundError:databasedoes not exist.ValueError:databasehas no_compactor_hnsw_recipetable (not a--skip-hnswartifact).RuntimeError: the bundledvssextension binary cannot be located.
replace_with_compacted() raises:
FileNotFoundError:sourceorcompactedis missing.FileExistsError:<source>.bakalready exists (it refuses to overwrite an existing backup).OSError: the move fromcompactedtosourcefails even via the cross-filesystem fallback (shutil.move).
🤖 GitHub Action
Run compaction in a workflow without installing uv or managing a Python toolchain. The action creates an isolated environment with the runner's preinstalled Python and runs compact in it.
- uses: it-bens/chunkhound-index-compactor@<commit-sha> # vX.Y.Z
with:
index-path: .chunkhound
replace: "true"
The inputs index-path, target, replace, and skip-hnsw map to the SOURCE, TARGET, --replace, and --skip-hnsw arguments above; only index-path is required. Pin to a commit SHA and let Renovate or Dependabot bump it from the trailing version comment.
The action wraps compact only; run restore separately (see CLI Usage above).
🚫 Not Supported
The tool fails hard rather than silently dropping anything it cannot reproduce.
- Non-
mainschemas and views (raiseValueError). - User-defined types, generated columns, self-referential foreign keys, and HNSW indexes on non-bare-column expressions (raise
ValueError). - Foreign-key cycles among tables (raise
ValueError). - HNSW tuning parameters other than
metric(M,M0,ef_construction,ef_search); they are not recoverable from a built index and are rebuilt at thevssdefaults. - Table and column comments are not carried across the rebuild.
See docs/architecture.md for the reasoning, and docs/out-of-scope.md for approaches considered and not pursued.
🏗️ Development
Setup, local checks, CI, and release process: CONTRIBUTING.md.
⚖️ License
MIT
[!NOTE] Yes, an AI wrote this README. And the code, the docs, the tests, and the
.claude/skillsit now uses to write the next round. Yes, a human told it to keep the emojis. The human has ADHD, which, as it turns out, means his brain was already doing attention re-routing and context-window thrashing before LLMs made it cool. They call him ... LLMartin. The emojis are a feature.
Metadata
Release files for chunkhound-index-compactor 0.6.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| chunkhound_index_compactor-0.6.0.tar.gz | 72.9 kB | With provenance | Details for chunkhound_index_compactor-0.6.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| chunkhound_index_compactor-0.6.0-py3-none-any.whl | Python 3 | none | any | Details for chunkhound_index_compactor-0.6.0-py3-none-any.whl |
Total release size: 85.4 kB
Release files / chunkhound_index_compactor-0.6.0.tar.gz
| Download URL | chunkhound_index_compactor-0.6.0.tar.gz |
|---|---|
| Size | 72.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
d30fc6399520644ff36a892eb99272fc9fb18d006ddbe8f8026ff6b30e7b9223
|
|
BLAKE2b-256 checksum How to use checksums |
c3b149b696655759286d21fd2826b05086683ba05f692bce01d46672d73de83c
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 26, 2026.
Transparency logRelease files / chunkhound_index_compactor-0.6.0-py3-none-any.whl
| Download URL | chunkhound_index_compactor-0.6.0-py3-none-any.whl |
|---|---|
| Size | 12.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c5326feee79f6286f0a8c8a2f3d5e671ab87eac25b380e1083e76c8bebe3c790
|
|
BLAKE2b-256 checksum How to use checksums |
74843a87045a2c55664f508d21532af8680316319948a9063a7ddbea6ee25014
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.13
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 26, 2026.
Transparency logSecurity for chunkhound-index-compactor 0.6.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for chunkhound_index_compactor-0.6.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on May 26, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.