Configuration

ChunkHound is configured through a JSON file, environment variables, and CLI flags.

Configuration File

Create .chunkhound.json in your project root. Here is a full example showing all sections:

Global Defaults (cross-project configuration)

To avoid copying the same settings into every project, place a global config in one of these locations (checked in order):

  • ~/.config/chunkhound/chunkhound.json
  • ~/.config/chunkhound/.chunkhound.json
  • ~/.chunkhound/chunkhound.json
  • ~/.chunkhound/.chunkhound.json
  • ~/chunkhound.json
  • ~/.chunkhound.json

You can also point to an arbitrary file with the CHUNKHOUND_GLOBAL_CONFIG_FILE environment variable.

Example global config (~/.config/chunkhound/chunkhound.json):

{
  "embedding": {
    "provider": "voyageai",
    "model": "voyage-code-4"
  },
  "llm": {
    "provider": "anthropic"
  },
  "indexing": {
    "exclude": ["**/node_modules/**", "**/.git/**", "**/dist/**"]
  }
}

Then in a specific project you only need a minimal .chunkhound.json for project-specific overrides (or nothing at all if the globals are sufficient):

{
  "embedding": {
    "api_key": "sk-..."   // only the key differs per machine
  }
}

Global values are deep-merged; project files win for any keys they specify.

Merging behavior:

  • Nested objects (embedding, llm, research, database, etc.): deep merge. You only need to specify the keys you want to change in a higher-priority file (local, explicit config, or CLI). Siblings from global (or lower layers) are preserved. This lets a project override just an api_key, switch llm.model, or change research.algorithm without losing other global settings.

  • Lists (indexing.exclude, indexing.include): the list supplied by the higher-priority source completely replaces any list from a lower layer (including global). ChunkHound’s built-in safe defaults are still layered on top of your list in the effective excludes (see get_effective_config_excludes()), and .gitignore interaction is controlled by exclude_mode.

Example — project overrides only the secret while inheriting provider + model from global (or switches providers entirely):

{
  "embedding": {
    "api_key": "sk-..."
  }
}

or to use a completely different provider for this project:

{
  "embedding": {
    "provider": "openai",
    "model": "text-embedding-3-large",
    "api_key": "sk-..."
  }
}

Example — project uses its own exclude list (replaces global’s list at the raw config level):

{
  "indexing": {
    "exclude": ["**/my-vendor/**", "**/generated/**"]
  }
}
{
  "database": {
    "provider": "duckdb",
    "path": ".chunkhound/db"
  },
  "embedding": {
    "provider": "voyageai",
    "model": "voyage-code-4",
    "batch_size": 100
  },
  "indexing": {
    "exclude": ["**/node_modules/**", "**/dist/**"],
    "exclude_mode": "combined",
    "per_file_timeout_seconds": 3.0,
    "batch_size": 50,
    "db_batch_size": 100,
    "detect_embedded_sql": true
  },
  "llm": {
    "provider": "anthropic",
    "utility_model": "claude-haiku-4-5-20251001",
    "synthesis_model": "claude-sonnet-4-5-20250929"
  }
}

Configuration Precedence

Settings are resolved in this order (highest priority first):

  1. CLI arguments — flags passed directly on the command line
  2. Config file — loaded via --config or CHUNKHOUND_CONFIG_FILE
  3. Local .chunkhound.json — auto-detected in the target directory
  4. Global defaults — CHUNKHOUND_GLOBAL_CONFIG_FILE or auto-discovered in ~/.config/chunkhound/ (or ~/.chunkhound/)
  5. Environment variables — CHUNKHOUND_* prefixed variables
  6. Defaults — built-in fallback values

Global defaults let you maintain shared settings (e.g. embedding provider + API key, common exclude patterns, LLM roles) in a single file so you do not need to copy .chunkhound.json into every project. Any project-local .chunkhound.json (or explicit config/CLI) overrides values from the global layer. Nested objects (embedding, llm, research, database, …) are deep-merged: specify only the keys you want to change and siblings from global survive. Lists such as indexing.exclude / include from a higher layer fully replace lower ones (built-in defaults are still applied on top; see Global Defaults above for details and examples).

Remote Configuration

For fleet-wide deployments, ChunkHound can fetch a configuration envelope from an operator-controlled HTTPS endpoint (or http:// to a loopback host for local development) at the start of every invocation, apply a list of rules to the on-disk global config, and seed the discovery-layer remote_config.url / auth_header into the global config file on the first successful fetch so subsequent runs continue fetching without needing the CLI flag or environment variable that triggered the initial run.

Skip this feature for single-user setups — enabling it adds a synchronous fetch with a hard 10-second wall-clock budget to every command (index, search, mcp, etc.), and there is no client-side cache. The intended use case is centralized management across many machines, not local convenience.

Enabling remote fetch

Two knobs, both operator-owned. They may be set via CLI, environment, or the global config file.

Trust boundary. A remote_config key inside a project-local .chunkhound.json or any file passed via --config is scrubbed at load time and a WARNING is logged — project-level files can never redirect the operator’s URL, because a checked-in file could otherwise point first-run fetches at an attacker-controlled endpoint. To persist activation on a machine, place the values in the global config file (~/.chunkhound.json or CHUNKHOUND_GLOBAL_CONFIG_FILE), or leave it to self-registration (below) after a first run with CLI/env.

SettingCLI flagEnvironment variable
Remote-config URL--remote-config-urlCHUNKHOUND_REMOTE_CONFIG__URL
Authorization header--remote-config-auth-headerCHUNKHOUND_REMOTE_CONFIG__AUTH_HEADER
# Env-var activation (persists for the shell session)
export CHUNKHOUND_REMOTE_CONFIG__URL="https://config.example.internal/chunkhound"
export CHUNKHOUND_REMOTE_CONFIG__AUTH_HEADER='Bearer ${MY_TOKEN}'
chunkhound index
# One-shot flag activation
chunkhound index \
  --remote-config-url https://config.example.internal/chunkhound \
  --remote-config-auth-header 'Bearer ${MY_TOKEN}'

Envelope format

The endpoint must return a JSON object:

{
  "version": 1,
  "min_chunkhound_version": "5.3.0",
  "rules": [
    { "id": "embedding.model", "op": "set", "value": "voyage-3.5" }
  ]
}
FieldTypeRequiredDescription
versionintegeryesEnvelope schema version. Must equal 1; any other value is rejected with envelope_version_unsupported.
min_chunkhound_versionstringnoPEP 440 version. If the running client is older, the fetch is discarded so a payload targeted at a newer format never lands on an older binary.
rulesarraynoOrdered list of rule objects (see below). Non-list values are rejected with schema_error.

Rules

Each rule mutates the working-copy config dict. Fields:

FieldTypeDescription
idstringDotted (a.b.c) or slash (a/b/c) path with an optional single leading . or /. Both forms accepted for copy-paste ergonomics.
opstringmerge (default when omitted), set, or remove.
valueanyRequired for merge and set; ignored by remove.
whenobjectOptional predicates gating this rule. See Predicates below.
min_chunkhound_versionstringOptional PEP 440 gate applied per-rule. If the running client is older, the rule is skipped with a WARNING (the envelope as a whole is still applied). Unparseable values log a distinct WARNING.

Operation semantics:

  • merge — deep-merge dicts. Collapses to set when either the incoming value or the existing leaf isn’t a dict (nothing to recurse into).
  • set — overwrite the leaf. Missing intermediate keys are created on write.
  • remove — delete the leaf. Missing paths are a no-op (logged).

Rules apply in list order; later rules overwrite earlier ones at the same leaf. Multiple rules may share the same id, and failures are attributed by both 1-based ordinal position and path in log messages so duplicates remain distinguishable.

{
  "version": 1,
  "rules": [
    { "id": "embedding",         "op": "merge", "value": { "model": "voyage-3.5", "batch_size": 100 } },
    { "id": "llm.timeout",       "op": "set",   "value": 180 },
    { "id": "indexing.exclude",  "op": "remove" }
  ]
}

Unknown-path handling. Typos are surfaced, not absorbed:

  • Unknown top-level paths (e.g. embeding.provider) log schema_error and skip the rule.
  • Unknown depth-2 paths (e.g. embedding.provder) are also detected and skipped, because sub-model extra="ignore" semantics would otherwise silently swallow the typo.

Predicates

when gates a rule against runtime state. Predicates are evaluated against a half-merged snapshot — the on-disk global config as it was before any rule ran — so predicate results don’t shift mid-loop as earlier rules mutate the working copy.

KeyValueMeaning
osstring or list of stringsMatches against Python’s sys.platform ("linux", "darwin", "win32", …). The rule applies iff the current platform is in the list.
existingstring or list of paths (dotted or slash form)Each path must resolve to a non-None value in the half-merged snapshot. Missing / null paths skip the rule.

Semantics:

  • All predicates must match (AND-logic). A single failing predicate skips the rule silently — the normal, non-error skip path, so log-scraping won’t see spurious WARNINGs from platform-scoped envelopes.
  • Unknown keys inside when (e.g. an unsupported key like arch, or an existng typo of existing) surface as schema_error and skip the rule with a WARNING, so authoring typos don’t quietly apply. Note: this check only fires for keys inside the when block — a typo of the outer key itself (e.g. "wehn": {...} at rule level) is indistinguishable from a rule that simply has no predicates, and applies unconditionally.
{
  "version": 1,
  "rules": [
    {
      "id": "embedding.model",
      "op": "set",
      "value": "voyage-3.5-lite",
      "when": { "os": ["linux", "darwin"] }
    },
    {
      "id": "llm.timeout",
      "op": "set",
      "value": 300,
      "when": { "existing": "llm.provider" },
      "min_chunkhound_version": "5.3.0"
    }
  ]
}

Trust boundary and refused paths

Some settings describe the local install and are protected against server override. After the rule loop, ChunkHound restores these paths from the pre-rules dict, regardless of what any rule did:

  • database.path — DB location is per-machine.
  • target_dir — the directory being indexed.
  • embeddings_disabled — operator kill-switch.
  • local_config_file, global_config_file, config_file — discovery inputs to the loader (allowing remote to override them would recurse).

remote_config.url and remote_config.auth_header are intentionally not refused wholesale — server-driven URL/header rotation is a supported migration path via envelope rules on those keys, and the self-registration step (below) seeds the on-disk value on the first successful fetch so a URL supplied only via CLI or env becomes durable. One narrow guard applies: a rule-set remote_config.url that fails the fetcher’s scheme rule (cleartext http:// to a non-loopback host) is reverted to the pre-rules on-disk value — or removed if there is none — with a WARNING. Persisting such a URL would brick the next fetch, since the fetcher refuses to send credentials over cleartext to a non-loopback host. Every https:// URL passes, so rotation to a new HTTPS origin is unrestricted; remote_config.auth_header has no scheme concept and is accepted verbatim.

Self-registration

The first successful fetch seeds the effective remote_config.url and remote_config.auth_header (from the winning discovery layer — CLI > global > env, matching ChunkHound’s overall config-layer precedence) into the global config file, so subsequent runs continue to fetch without needing the CLI flag or environment variable that triggered the initial run. Precedence:

  • If a rule in the envelope set remote_config.url or remote_config.auth_header, the rule’s value wins and is persisted (server-driven URL/header rotation). For remote_config.url this requires the value to pass the fetcher’s scheme rule; an unsafe URL rule (cleartext http:// to a non-loopback host) is reverted to the on-disk value or removed with a WARNING before this step runs. When the revert leaves the slot empty, discovery-layer seeding still fires — a first-run CLI/env URL survives even when the same envelope also pushed a bad URL rule.
  • Otherwise, if the on-disk file has no value at that path (and no rule set one this run), the discovery-layer value is seeded.
  • If the on-disk file already carries a value at that path, it is left alone — self-registration is gap-fill only, never a rewrite.

The target is:

  1. CHUNKHOUND_GLOBAL_CONFIG_FILE if set, else
  2. the first existing candidate from the global-config search path, else
  3. ~/.chunkhound.json (created if absent).

Writes are atomic (sibling .tmp + replace()), and any pre-existing target file is backed up as <target>.bak first.

On-disk values are durable. Once a URL or header is in the global config file — whether seeded by a first-time fetch, hand-edited by an operator, or written by an op: set rule — a later run with a differing --remote-config-url / --remote-config-auth-header (or env-var equivalent) will fetch against that transient value but will not overwrite what’s on disk. This prevents a one-off invocation from silently replacing a fleet-wide URL. To change a persisted value, edit the global config file directly or push a rule that sets it.

Warning: A literal auth_header value lands on disk in plaintext under the user’s account. ChunkHound restricts the file to the owning user where the OS supports it, but this is not an encryption boundary — anyone who can read the account’s files (privileged local processes, backups, disk images) reads the token verbatim. To persist only a placeholder, use ${VAR} interpolation:

chunkhound index --remote-config-auth-header 'Bearer ${MY_TOKEN}'

The raw templated string Bearer ${MY_TOKEN} is what gets stored; ${MY_TOKEN} is resolved against os.environ at fetch time. If any referenced variable is unset or empty, the entire Authorization header is dropped and a WARNING is logged — a partial interpolation would leak the literal placeholder to the wire.

Delta-only validation gate

After rules apply, the resulting config is snapshot-validated against the current command and every persistence-hazard command (currently index, mcp, and research). Each snapshot returns a set of structured ConfigErrorCode values (see Startup validation). The pipeline compares pre-rules and post-rules error sets on two substrates; both must accept (E_post ⊆ E_pre) or the fetch is discarded:

  1. Persisted global dict — the JSON that would be written, with env / local / --config / CLI skipped, plus worst-case command substitutions (for example forcing mcp.transport=http so host/CORS guards enumerate). This stops a one-off --auth-token, a project-local .chunkhound.json, or a standing CHUNKHOUND_MCP__* env var from masking a newly written global hazard.
  2. Active invocation — every config layer of the current process (CLI, --config, local, global, env), plus the same worst-case substitutions. This stops a rule that is only unsafe in combination with this process’s other sources.

If either comparison introduces a new code, nothing is written and an ERROR is logged naming the substrate (persisted or active), the command, and the code.

This prevents a well-intentioned rule change during a search invocation from silently breaking the next mcp startup or scheduled research run on the same machine, and prevents an overlay on the current process from laundering an unsafe value into the global file.

Fetch behavior and failure model

  • Synchronous on every invocation that goes through create_validated_config.
  • Hard 10-second wall-clock budget enforced by asyncio.wait_for on top of the per-phase httpx timeout, so a slow-drip server cannot stall startup indefinitely.
  • Single attempt. No retries. No client-side cache.
  • Recoverable failures (timeout, transport error, non-2xx response, JSON parse error, envelope validation, min_chunkhound_version gate) log a WARNING and the invocation proceeds against whatever is currently on disk. This is intentional — remote-config outages must not brick indexing or search.
  • Disk-write failures during backup or persist escalate to sys.exit(1) with the target path and errno on stderr. A silent write failure would leave the process running against a stale on-disk copy while advertising success.
  • httpx runs with trust_env=False, so HTTPS_PROXY, HTTP_PROXY, NO_PROXY, SSL_CERT_FILE, SSL_CERT_DIR, and netrc are ignored on the remote-config fetch. This is deliberate: a hostile HTTPS_PROXY could reroute the credentialed request, and a MITM proxy holding a system-trusted CA could terminate the tunnel and read the Authorization header. Operational consequence — an operator needing a private / internal CA must extend the system trust store (e.g. install the CA into /etc/ssl/certs, the OS keychain, or the process’s baked-in bundle); pointing SSL_CERT_FILE at an internal bundle has no effect and the TLS handshake will fail. The failure looks like any other recoverable fetch WARNING (“Remote-config fetch failed”), which can be mistaken for a flaky server rather than an ignored CA bundle.

Startup validation

The delta-only gate above relies on structured error codes returned by Config.validate_for_command_structured(). These are also surfaced in normal CLI startup errors alongside their human-readable messages, so log-scraping and monitoring can key off stable identifiers:

CodeTrigger
missing_required_configRequired top-level configuration is absent for the command.
llm_not_configuredNo LLM provider configured for a command that needs one.
llm_missing_role_configAn LLM role (utility, synthesis, map_hyde, autodoc_cleanup) is missing its provider/model.
embedding_not_configuredNo embedding provider, or the provider is missing required fields for the command.
mcp_non_loopback_no_authmcp.host binds to a non-loopback interface without mcp.auth_token.
mcp_cors_no_authmcp.cors is enabled without mcp.auth_token.
db_readonly_wrong_commanddatabase.read_only is true for a command other than mcp / _quickresearch.
db_readonly_non_duckdbDuring mcp, database.read_only is true with a non-DuckDB provider. (Non-mcp/_quickresearch commands hit db_readonly_wrong_command first.)

Embedding Providers

ProviderConfig ValueEnv VarDefault ModelNotes
VoyageAIvoyageaiCHUNKHOUND_EMBEDDING__API_KEYvoyage-3.5Set model to voyage-code-4 for code search
OpenAIopenaiCHUNKHOUND_EMBEDDING__API_KEYtext-embedding-3-smallWidely available

VoyageAI Models

All models below accept output_dims of 256, 512, 1024 (default), or 2048. ChunkHound uses the per-batch token limit to size embedding requests, so picking a model with a larger limit means fewer round trips when indexing.

ModelContextTokens per batchNotes
voyage-code-432K320KCode-specialized, built for coding-agent retrieval. Recommended for code search
voyage-432K320KGeneral purpose, balanced cost and quality
voyage-4-large32K120KGeneral purpose, highest retrieval quality
voyage-4-lite32K1MGeneral purpose, lowest cost and latency
voyage-code-332K120KPrevious-generation code model
voyage-3.532K320KDefault. Previous-generation general purpose
voyage-3.5-lite32K1MPrevious-generation lite
voyage-3-large32K120KPrevious-generation large
voyage-finance-232K120KFinance domain, 1024 dims only
voyage-law-216K120KLegal domain, 1024 dims only
voyage-multilingual-232K120KMultilingual, 1024 dims only

Models outside this list still work. ChunkHound discovers their dimensions at runtime and falls back to a conservative 320K token batch limit.

Changing the embedding model

An index built with one embedding configuration cannot be searched with another. Search reads the stored vectors matching the query’s dimensions and filters them by provider and model, so a different model, or the same model at a different output_dims, returns nothing. Re-indexing does not repair a dimensions change either, because chunks that already have vectors for the provider and model are skipped at any dimension. Re-embedding under a new model costs tokens and time, and the superseded vectors stay in the database until removed.

The index therefore keeps the model and dimensions it was built with. Changing embedding.model or embedding.output_dims is a proposal, not an instruction:

ContextBehavior
Interactive chunkhound index, different modelWarns, shows the chunk count, and asks whether to re-embed. Declining keeps the indexed model and dimensions and asks again next run.
Same model, different output_dimsKeeps the indexed dimensions without asking, since the change cannot be re-embedded in place.
Non-interactive (CI, CHUNKHOUND_NO_PROMPTS=1)Warns and keeps the indexed model and dimensions. Nothing is re-embedded.
MCP server startupKeeps the indexed model and dimensions silently, logging the reason to stderr.
Different provider configuredCannot be kept (credentials and dimensions differ), so the configured provider is used and every chunk is re-embedded.

If the index’s dimensions are not valid for its model under the current settings, which can happen for an index built through a custom endpoint, ChunkHound keeps the model, warns that searches will not match, and still starts.

Which model and dimensions an index uses is recorded in a small file beside the database (chunks.db.embedding.json for DuckDB, lancedb.lancedb.embedding.json for LanceDB). It is written the first time an index with vectors is opened and again when a switch is accepted, so a finished or interrupted switch is remembered rather than guessed from vector counts. It is never written for a read-only database. If the file cannot be read, ChunkHound warns, falls back to counting vectors, and leaves the file alone. Both DuckDB and LanceDB indexes are covered.

To adopt a new model deliberately, accept the prompt. To change dimensions, or to start clean, delete the database directory and re-index. To stop being asked, set embedding.model and embedding.output_dims to whatever the index already holds.

When a newer model supersedes the one in use, an interactive chunkhound index prints a one-line suggestion. It never acts on its own and does not appear in non-interactive runs. Set CHUNKHOUND_NO_MODEL_SUGGESTIONS=1 to silence it.

Embedding Options

OptionTypeDefaultDescription
base_urlstringnullCustom embedding endpoint. Required for self-hosted OpenAI-compatible embeddings.
ssl_verifybooleantrueVerify TLS certificates for requests sent to base_url. Ignored when base_url is unset.
rerank_modelstringnullReranking model name (enables multi-hop reranking)
rerank_urlstringnullSeparate rerank endpoint URL (optional when reranking is served from base_url)
rerank_ssl_verifybooleannullVerify TLS certificates for rerank requests. Inherits ssl_verify when unset.
rerank_formatstring"auto"Reranking API format: cohere, tei, or auto
rerank_batch_sizenumbernullMax documents per rerank request
timeoutnumber30Request timeout in seconds
max_retriesnumber3Max retry attempts on failure
api_versionstringnullAzure OpenAI API version (YYYY-MM-DD)
azure_endpointstringnullAzure OpenAI endpoint (mutually exclusive with base_url)
azure_deploymentstringnullAzure OpenAI deployment name

Database Backends

BackendStatusRecommended
duckdbStableYes — use this
lancedbExperimentalNo — for evaluation only

DuckDB (default)

Stable — recommended for all use cases.

Fast analytical queries and efficient storage.

{
  "database": {
    "provider": "duckdb",
    "path": ".chunkhound/db"
  }
}

DuckDB storage paths

database.path normally names a directory; ChunkHound stores the database as chunks.db inside it. A path ending in .db or .duckdb is an explicit DuckDB file instead. An existing regular file is also kept as a direct database path for older installations. If no path is configured, the default is <project-root>/.chunkhound/db/chunks.db.

Gotchas:

  • Prefer passing the project directory as a positional argument (chunkhound search "query" /path/to/project) so its .chunkhound.json resolves correctly.
  • A --db path pointing at the wrong subpath silently returns 0 results — no error, just empty. Verify with a regex search first.
  • Pre-v4 flat .chunkhound files block directory creation — move them aside before re-indexing.
  • --config does not override a project-local .chunkhound.json for the DB path — use an explicit --db when the target project has its own config.

LanceDB

Experimental — not recommended for production use. The LanceDB integration is actively developed but may have rough edges around index rebuilding, migration, and edge-case query correctness. Use DuckDB unless you are evaluating LanceDB specifically.

{
  "database": {
    "provider": "lancedb",
    "path": ".chunkhound/db"
  }
}

Database Options

OptionTypeDefaultDescription
max_disk_usage_mbnumbernullMax DB size in MB before indexing stops (CLI flag uses GB)
fragmentation_threshold_pctnumber30Background/auto-compaction trigger: file-size overhead above the provider’s estimated live DB size (%). 30 = compact when the DB is ~30% larger than live data. 0 = always, null = never. This does not disable the fixed chunkhound index compaction boundaries. CLI: --fragmentation-threshold-pct.
execute_timeout_secondsnumbernullTimeout for synchronous serial DB executor waits (execute_sync / _execute_in_db_thread_sync) in seconds. null = built-in defaults (30s normal ops, 660s compaction). When set, replaces both defaults for every sync operation including compaction, HNSW rebuild, and queries. Does not apply to async dispatch (execute_async), which remains unbounded. CLI: --db-execute-timeout. Env: CHUNKHOUND_DATABASE__EXECUTE_TIMEOUT_SECONDS (or legacy CHUNKHOUND_DB_EXECUTE_TIMEOUT).
lancedb_index_typestringnullLanceDB vector index type: auto, ivf_hnsw_sq, or ivf_rq
lancedb_optimize_fragment_thresholdnumber100Fragment count to trigger LanceDB compaction

DuckDB also compacts during chunkhound index at two fixed batch boundaries: once after chunking and before embedding generation, then again at the end of the indexing pass. Those boundary calls are unconditional, including --no-embeddings and noop re-index runs. If a batch compaction fails with status: "error", the index run is aborted. Providers that report compaction as unsupported/skipped keep indexing normally. Sampled/background auto-compaction (triggered by fragmentation threshold during normal operations) does NOT fail the original operation — failures are logged and skipped.

DuckDB compaction rebuilds a fresh canonical ChunkHound database file and swaps it into place atomically. This is intentionally not a generic DuckDB passthrough: only ChunkHound-owned canonical tables (schema_version, files, chunks, embeddings_*) are preserved. Any unknown or non-canonical tables are dropped during compaction by design.

Indexing Options

OptionTypeDefaultDescription
excludestring[]built-in listGlob patterns to exclude from indexing
includestring[]all supported file typesGlob patterns limiting which files are indexed; files not matching any pattern are skipped
exclude_modestringnullcombined, config_only, or gitignore_only. When an explicit exclude list is provided, defaults to "combined"; otherwise defaults to "gitignore_only"
force_reindexbooleanfalseForce re-indexing of all files
max_concurrentnumber5Max concurrent parser workers
cleanupbooleantrueRemove orphaned DB records after indexing
max_file_size_mbnumber10Skip files larger than this (MB)
config_file_size_threshold_kbnumber20Skip structured config files (JSON/YAML/TOML) larger than this (KB); 0 to disable
per_file_timeout_secondsnumber3.0Max parse time per file (0 to disable)
batch_sizenumber50Files per parsing batch
db_batch_sizenumber100Chunks per database write batch
detect_embedded_sqlbooleantrueIndex SQL in string literals
per_file_timeout_min_size_kbnumber128Only apply per-file timeout to files at least this large (KB)

By default, ChunkHound excludes common noise directories (node_modules, dist, __pycache__, .git, lock files, build artifacts). Set exclude_mode: "config_only" and exclude: [] to start with a clean slate.

Exclude Modes

  • combined (default when custom exclude patterns are provided) — merges .gitignore rules with your indexing.exclude patterns
  • config_only — only uses patterns from indexing.exclude, ignores .gitignore
  • gitignore_only (default when no custom exclude patterns are provided) — only uses .gitignore rules, ignores config excludes

LLM Configuration

The LLM provider is used for deep code research (chunkhound research and the code_research MCP tool).

ProviderConfig ValueEnv VarUtility DefaultSynthesis DefaultNotes
Claude Code CLIclaude-code-cli—claude-haiku-4-5-20251001claude-haiku-4-5-20251001Uses local Claude Code installation
Codex CLIcodex-cli—codexcodexUses local Codex CLI installation
OpenCode CLIopencode-cli—opencode/grok-codeopencode/grok-codeUses local OpenCode CLI installation
AnthropicanthropicCHUNKHOUND_LLM_API_KEYclaude-haiku-4-5-20251001claude-sonnet-4-5-20250929Direct API access
OpenAIopenaiCHUNKHOUND_LLM_API_KEYgpt-5-nanogpt-5Direct API access
GeminigeminiCHUNKHOUND_LLM_API_KEYMust be set explicitly via CHUNKHOUND_LLM_MODEL or llm.model (configurator defaults to gemini-3.5-flash)Must be set explicitly via CHUNKHOUND_LLM_MODEL or llm.model (configurator defaults to gemini-3.5-flash)Google Gemini API. Migration: CHUNKHOUND_GEMINI_MODEL was removed in v4.x — rename to CHUNKHOUND_LLM_MODEL.
GrokgrokCHUNKHOUND_LLM_API_KEYMust be set explicitly (configurator defaults to grok-4.3)Must be set explicitly (configurator defaults to grok-4.3)xAI API. Registry providers require explicit model.
DeepSeekdeepseekCHUNKHOUND_LLM_API_KEYMust be set explicitly (configurator defaults to deepseek-v4-flash)Must be set explicitly (configurator defaults to deepseek-v4-flash)DeepSeek API. Registry providers require explicit model.
OpenRouteropenrouterCHUNKHOUND_LLM_API_KEYMust be set explicitlyMust be set explicitlyOpenRouter API. Registry providers require explicit model.
OrcaRouterorcarouterCHUNKHOUND_LLM_API_KEYMust be set explicitlyMust be set explicitlyOrcaRouter API. Registry providers require explicit model.
RequestyrequestyCHUNKHOUND_LLM_API_KEYMust be set explicitlyMust be set explicitlyRequesty API. Registry providers require explicit model.

"model" is a convenience shorthand that sets both utility_model and synthesis_model to the same value. To use different models per role, set utility_model and synthesis_model explicitly.

When an OpenAI-compatible LLM provider points at a custom base_url, ChunkHound treats it as a generic custom backend. In that mode you must set an explicit model name; ChunkHound does not guess a local default. This applies to provider: "openai", to registry providers (DeepSeek, Grok, OpenRouter, OrcaRouter, and Requesty) when routed through a non-canonical endpoint, and to per-role overrides that resolve to those providers.

LLM Options

OptionTypeDefaultDescription
utility_providerstringnullOverride provider for utility operations
synthesis_providerstringnullOverride provider for synthesis operations
timeoutnumber120LLM request timeout in seconds
max_retriesnumber3Max retry attempts
output_limits_enabledbooleanfalseRestore the exact legacy numeric output limits for research synthesis instead of provider-managed limits.
output_limit_fallbacknumber64000Positive output-token fallback used when a synthesis provider cannot authoritatively omit a limit or declare one.
codex_reasoning_effortstringnullDefault reasoning effort for Codex/OpenAI: minimal, low, medium, high, xhigh
codex_reasoning_effort_utilitystringnullReasoning effort override for utility stage
codex_reasoning_effort_synthesisstringnullReasoning effort override for synthesis stage

Research Synthesis Output Limits

Provider-managed output limits are the default (llm.output_limits_enabled: false). This policy applies only to the final research synthesis path: single-pass synthesis and the map and reduce calls of map-reduce synthesis. Non-research LLM operations continue to use their existing explicit numeric caps unchanged.

For each provider-managed synthesis request, ChunkHound uses this precedence without guessing limits from model names:

  1. If the selected synthesis provider authoritatively supports omitting the output cap, omit it and let the provider manage the limit.
  2. Otherwise, use an authoritative declared positive maximum only when it includes a durable provider/API source.
  3. Otherwise, send the scalar llm.output_limit_fallback (default 64000).

UNKNOWN omission capability is handled conservatively: ChunkHound does not assume omission is safe, so it uses a valid sourced declaration or the scalar fallback. This is intentionally not a per-model lookup table.

Built-in DeepSeek, Grok, and OpenRouter configurations at their canonical endpoints authoritatively support omission. Provider-managed DeepSeek and OpenRouter requests omit max_tokens, and provider-managed Grok Chat Completions requests omit max_completion_tokens. Setting a custom base_url on any of these built-ins downgrades omission capability to UNKNOWN; generic OpenAI-compatible endpoints are also UNKNOWN and therefore use a sourced declaration or the configured fallback. An omitted client cap lets the provider apply its own policy—it does not mean output is unlimited.

At research startup, the progress display reports the resolved synthesis request-limit policy using one of these forms (runtime cap values are comma-formatted):

  • Max depth: 1; synthesis request limits: provider-managed (cap omitted)
  • Max depth: 1; synthesis request limits: provider-managed (provider-declared cap: 64,000 tokens)
  • Max depth: 1; synthesis request limits: provider-managed (fallback cap: 64,000 tokens)
  • Max depth: 1; synthesis request limits: legacy numeric (30,000-token single/reduce cap; computed per-map caps)

To roll back exactly to the legacy behavior, set llm.output_limits_enabled: true:

{
  "llm": {
    "output_limits_enabled": true
  }
}

Legacy mode preserves a 30000-token numeric request for single-pass and reduce synthesis. Each map request uses max(5000, int(30000 * cluster_tokens / total_input_tokens)); when total input tokens are zero, every map uses 30000. Provider-managed mode changes only the request’s transport allowance. Prompt guidance remains separate: 15000 for single-pass and reduce synthesis, and min(legacy_map_allowance, 7500) for each map.

Operational caveats:

  • Provider-managed output does not guarantee that the provider will not truncate a response.
  • Native provider truncation signals still raise RuntimeError; synthesis does not retry the truncated stage or return a partial or degraded result.
  • When one concurrent map fails, local sibling cancellation is best-effort. It is not a guarantee that an already-dispatched remote request stopped or that the provider will not bill it.

Anthropic-specific Options

These apply when the active provider (or a role provider) is anthropic. Each option also has a matching CHUNKHOUND_LLM_ANTHROPIC_<OPTION> environment variable (single underscore, uppercased).

OptionTypeDefaultDescription
anthropic_thinking_enabledbooleanfalseEnable extended thinking.
anthropic_thinking_modestringnullauto (default when unset), off, manual, or adaptive. auto selects adaptive on Opus 4.6+/Sonnet 4.6 and manual on older models.
anthropic_thinking_budget_tokensnumber10000Manual-mode thinking budget (min 1024). Ignored in adaptive mode (Opus 4.6+).
anthropic_thinking_displaystringnullAdaptive-mode thinking text: summarized or omitted. Opus 4.7/4.8 omit by default.
anthropic_interleaved_thinkingbooleanfalseManual-mode interleaved thinking between tool calls. Auto-enabled in adaptive mode.
anthropic_effortstringnullToken-usage effort: low, medium, high, xhigh, max. xhigh is Opus 4.7/4.8 only; max is Opus 4.6+. Unsupported levels are dropped with a warning.
anthropic_task_budget_tokensnumbernullAdvisory agentic-loop token budget (beta). Opus 4.7/4.8 only; minimum 20000.
anthropic_prompt_cachingbooleanfalseSend cache_control so the Messages API can cache prompt prefixes.
anthropic_cache_ttlstringnullPrompt-cache TTL such as 1h. null uses the API default of 5 minutes.
anthropic_context_management_enabledbooleanfalseAutomatic clearing of tool results and thinking blocks (beta).
anthropic_clear_thinking_keep_turnsnumbernullThinking turns to keep when context management clears them. null keeps all.
anthropic_clear_tool_uses_trigger_tokensnumbernullInput-token threshold that triggers tool-result clearing.
anthropic_clear_tool_uses_keepnumbernullNumber of recent tool-use pairs to keep after clearing.

Gemini-specific Options

These apply when the active provider (or a role provider) is gemini. Matching environment variables use the CHUNKHOUND_LLM_GEMINI_* prefix, and the CLI exposes --llm-gemini-thinking-level / --llm-gemini-thinking-budget.

OptionTypeDefaultDescription
gemini_thinking_levelstringnullAdaptive thinking depth for Gemini 3+ models. Allowed values: low, medium, high. Forwarded to the Google Gen AI SDK as thinking_level.
gemini_thinking_budgetnumbernullFixed thinking token budget for Gemini 2.5+ models. Forwarded to the Google Gen AI SDK as thinking_budget.

Both options can be set independently — thinking_level controls adaptive depth (Gemini 3+), while thinking_budget sets a fixed token cap (Gemini 2.5+). If both are unset, ChunkHound sends no Gemini thinking config and the model uses its own defaults.

Research Configuration

Controls the code_research MCP tool and chunkhound research command.

OptionTypeDefaultEnv VarDescription
algorithm"v1"|"v2"|"v3""v3"CHUNKHOUND_RESEARCH_ALGORITHMResearch algorithm version
query_expansion_enabledbooltrueCHUNKHOUND_RESEARCH_QUERY_EXPANSION_ENABLEDLLM-based query expansion for broader coverage
num_expanded_queriesint2CHUNKHOUND_RESEARCH_NUM_EXPANDED_QUERIESNumber of additional queries to generate (1-5)
initial_page_sizeint30CHUNKHOUND_RESEARCH_INITIAL_PAGE_SIZEResults per vector query in multi-hop search (10-100)
relevance_thresholdnumber0.5CHUNKHOUND_RESEARCH_RELEVANCE_THRESHOLDMin rerank score for chunk inclusion (0.3-0.8)
max_symbolsint5CHUNKHOUND_RESEARCH_MAX_SYMBOLSMax symbols to extract for regex search augmentation (1-20)
regex_augmentation_rationumber0.3CHUNKHOUND_RESEARCH_REGEX_AUGMENTATION_RATIORegex target as fraction of semantic count (0.1-1.0)
regex_min_resultsint20CHUNKHOUND_RESEARCH_REGEX_MIN_RESULTSMin regex results regardless of augmentation ratio (10-100)
regex_scan_page_sizeint100CHUNKHOUND_RESEARCH_REGEX_SCAN_PAGE_SIZEInternal pagination batch size for regex exclusion scanning (50-200)
multi_hop_time_limitnumber5.0CHUNKHOUND_RESEARCH_MULTI_HOP_TIME_LIMITMax seconds for evidence expansion (1.0-15.0)
multi_hop_result_limitint500CHUNKHOUND_RESEARCH_MULTI_HOP_RESULT_LIMITMax chunks accumulated during multi-hop expansion (100-2000)
multi_hop_min_candidatesint5CHUNKHOUND_RESEARCH_MULTI_HOP_MIN_CANDIDATESMin candidates above threshold to continue expansion (1-20)
multi_hop_score_degradationnumber0.15CHUNKHOUND_RESEARCH_MULTI_HOP_SCORE_DEGRADATIONMax score drop in top-5 before terminating expansion (0.05-0.5)
multi_hop_min_relevancenumber0.3CHUNKHOUND_RESEARCH_MULTI_HOP_MIN_RELEVANCEQuality floor for expansion candidates (0.1-0.8)
depth_exploration_enabledbooltrueCHUNKHOUND_RESEARCH_DEPTH_EXPLORATION_ENABLEDEnable depth exploration to find more chunks in discovered files
max_exploration_filesint5CHUNKHOUND_RESEARCH_MAX_EXPLORATION_FILESMax files to explore for additional aspects, top-K by score (1-15)
exploration_queries_per_fileint2CHUNKHOUND_RESEARCH_EXPLORATION_QUERIES_PER_FILENumber of aspect-based queries to generate per file (1-3)
depth_exploration_max_completion_tokensint10000CHUNKHOUND_RESEARCH_DEPTH_EXPLORATION_MAX_COMPLETION_TOKENSToken budget for depth exploration query generation (1-50000)
min_gapsint1CHUNKHOUND_RESEARCH_MIN_GAPSMinimum gaps to process after selection (0-5)
max_gapsint10CHUNKHOUND_RESEARCH_MAX_GAPSMaximum gaps to fill after selection (5-30)
gap_similarity_thresholdnumber0.25CHUNKHOUND_RESEARCH_GAP_SIMILARITY_THRESHOLDCosine distance threshold for clustering similar gaps (0.1-0.5)
shard_budgetint40000CHUNKHOUND_RESEARCH_SHARD_BUDGETToken budget per gap detection shard for LLM processing (20000-60000)
min_cluster_sizeint5CHUNKHOUND_RESEARCH_MIN_CLUSTER_SIZEMinimum cluster size for HDBSCAN clustering (1-20)
target_tokensint20000CHUNKHOUND_RESEARCH_TARGET_TOKENSOutput token budget for final synthesis (10000-100000)
max_compression_iterationsint5CHUNKHOUND_RESEARCH_MAX_COMPRESSION_ITERATIONSMax compression loop iterations before error (1-10)
max_boundary_expansion_linesint300CHUNKHOUND_RESEARCH_MAX_BOUNDARY_EXPANSION_LINESMax lines to expand for complete functions/classes (50-500)
max_chunks_per_file_reprint5CHUNKHOUND_RESEARCH_MAX_CHUNKS_PER_FILE_REPRTop chunks per file for representative document creation (1-10)
max_tokens_per_file_reprint2000CHUNKHOUND_RESEARCH_MAX_TOKENS_PER_FILE_REPRToken limit per file representative document (500-5000)
context_windowint150000CHUNKHOUND_RESEARCH_CONTEXT_WINDOWMax tokens for LLM context window (50000-200000)
compression_max_depthint10CHUNKHOUND_RESEARCH_COMPRESSION_MAX_DEPTHMax recursion depth for hierarchical compression (1-20)
final_synthesis_thresholdint75000CHUNKHOUND_RESEARCH_FINAL_SYNTHESIS_THRESHOLDMax tokens for final synthesis LLM call (30000-200000)
window_expansion_enabledbooltrueCHUNKHOUND_RESEARCH_WINDOW_EXPANSION_ENABLEDEnable neighboring chunk expansion for context
window_expansion_linesint50CHUNKHOUND_RESEARCH_WINDOW_EXPANSION_LINESLines to expand before/after retrieved chunks (10-200)
import_resolution_enabledbooltrueCHUNKHOUND_RESEARCH_IMPORT_RESOLUTION_ENABLEDAutomatically fetch source files for imports in retrieved chunks
import_resolution_max_filesint10CHUNKHOUND_RESEARCH_IMPORT_RESOLUTION_MAX_FILESMax import source files to fetch per synthesis (1-50)
exhaustive_modeboolfalseCHUNKHOUND_RESEARCH_EXHAUSTIVE_MODEEnable exhaustive retrieval (no result limit, 600s timeout)
exhaustive_time_limitnumber600.0CHUNKHOUND_RESEARCH_EXHAUSTIVE_TIME_LIMITSafety timeout for exhaustive mode in seconds (60-1800)
{
  "research": {
    "algorithm": "v3",
    "exhaustive_mode": false,
    "target_tokens": 20000,
    "query_expansion_enabled": true,
    "depth_exploration_max_completion_tokens": 10000,
    "relevance_threshold": 0.5
  }
}

Algorithm Versions

The algorithm setting controls how ChunkHound explores your codebase to answer a research question. All three versions produce the same output format; they differ only in how thoroughly they search.

New to ChunkHound? Start with "v3" (the default).

VersionStrategyLLM callsBest for
v1BFS — generates follow-up questions, explores one level deepMinimalQuick lookups, simple codebases
v2Wide coverage — depth-first on top files, then gap detectionMediumBalanced discovery; most production use cases
v3 (default)Runs v1 + v2 in parallel, merges resultsMost (parallel, not sequential)Complex codebases where missing context is costly

v3 is not slower than v2 — both strategies run concurrently via asyncio.gather, so the wall-clock time is roughly the same as v2 alone while covering more ground.

When to switch away from v3:

  • Use v1 when cost matters and the question is narrow and self-contained (“explain how the config loader works”)
  • Use v2 when you want a good balance without the extra LLM spend of dual-strategy merging
  • v3 is the right default for open-ended research questions (“how does authentication flow through this system?”)

Gap detection parameters (min_gaps, max_gaps, gap_similarity_threshold) only affect v2 and v3. They are silently ignored for v1.

Environment Variables

Most environment variables use the CHUNKHOUND_ prefix with __ (double underscore) as the section delimiter. The LLM section uses a single underscore (CHUNKHOUND_LLM_*).

VariableDescription
CHUNKHOUND_EMBEDDING__PROVIDEREmbedding provider name
CHUNKHOUND_EMBEDDING__MODELEmbedding model name
CHUNKHOUND_EMBEDDING__API_KEYAPI key for embedding provider
CHUNKHOUND_EMBEDDING__BASE_URLBase URL for OpenAI-compatible endpoints
CHUNKHOUND_EMBEDDING__SSL_VERIFYVerify TLS certificates for embedding requests sent to base_url
CHUNKHOUND_EMBEDDING__RERANK_MODELReranking model name
CHUNKHOUND_EMBEDDING__RERANK_URLSeparate rerank endpoint URL
CHUNKHOUND_EMBEDDING__RERANK_SSL_VERIFYVerify TLS certificates for rerank requests (overrides ssl_verify)
CHUNKHOUND_EMBEDDING__RERANK_FORMATReranking API format: cohere, tei, or auto
CHUNKHOUND_EMBEDDING__RERANK_BATCH_SIZEMax documents per rerank request
CHUNKHOUND_EMBEDDING__TIMEOUTRequest timeout in seconds (default: 30)
CHUNKHOUND_EMBEDDING__MAX_RETRIESMax retry attempts on failure (default: 3)
CHUNKHOUND_EMBEDDING__API_VERSIONAzure OpenAI API version (YYYY-MM-DD)
CHUNKHOUND_EMBEDDING__AZURE_ENDPOINTAzure OpenAI endpoint
CHUNKHOUND_EMBEDDING__AZURE_DEPLOYMENTAzure OpenAI deployment name
CHUNKHOUND_DATABASE__PROVIDERDatabase backend (duckdb or lancedb)
CHUNKHOUND_DATABASE__PATHDatabase storage path
CHUNKHOUND_DATABASE__MAX_DISK_USAGE_GBMax database size in GB
CHUNKHOUND_DATABASE__EXECUTE_TIMEOUT_SECONDSSync serial DB executor timeout in seconds (overrides 30s/660s defaults when set; async dispatch unbounded)
CHUNKHOUND_DATABASE__FRAGMENTATION_THRESHOLD_PCTAuto-compaction fragmentation threshold (%)
CHUNKHOUND_DATABASE__READ_ONLYOpen DB read-only (true/1/yes)
CHUNKHOUND_DATABASE__LANCEDB_INDEX_TYPELanceDB vector index type
CHUNKHOUND_DATABASE__LANCEDB_OPTIMIZE_FRAGMENT_THRESHOLDLanceDB fragment count to trigger optimize
CHUNKHOUND_REMOTE_CONFIG__URLURL to fetch remote configuration on startup (see Remote Configuration)
CHUNKHOUND_REMOTE_CONFIG__AUTH_HEADERAuthorization header for the remote-config fetch; supports ${VAR} interpolation against the environment at fetch time
CHUNKHOUND_LLM_PROVIDERLLM provider for research
CHUNKHOUND_LLM_MODELLLM model shorthand that sets both utility and synthesis roles
CHUNKHOUND_LLM_UTILITY_MODELLLM model for utility tasks (fast, lower cost)
CHUNKHOUND_LLM_SYNTHESIS_MODELLLM model for synthesis tasks (primary output)
CHUNKHOUND_LLM_API_KEYAPI key for LLM provider
CHUNKHOUND_LLM_BASE_URLBase URL for LLM provider (proxy / custom endpoint)
CHUNKHOUND_LLM_SSL_VERIFYVerify TLS certificates for requests sent to llm.base_url
CHUNKHOUND_LLM_UTILITY_PROVIDEROverride provider for utility operations
CHUNKHOUND_LLM_SYNTHESIS_PROVIDEROverride provider for synthesis operations
CHUNKHOUND_LLM_TIMEOUTLLM request timeout in seconds (default: 120)
CHUNKHOUND_LLM_MAX_RETRIESMax retry attempts (default: 3)
CHUNKHOUND_LLM_OUTPUT_LIMITS_ENABLEDRestore exact legacy research synthesis output limits (true/false; default: false)
CHUNKHOUND_LLM_OUTPUT_LIMIT_FALLBACKPositive provider-managed synthesis fallback in output tokens (default: 64000)
CHUNKHOUND_LLM_CODEX_REASONING_EFFORTReasoning effort for Codex models (minimal, low, medium, high, xhigh)
CHUNKHOUND_LLM_CODEX_REASONING_EFFORT_UTILITYReasoning effort override for utility stage
CHUNKHOUND_LLM_CODEX_REASONING_EFFORT_SYNTHESISReasoning effort override for synthesis stage
CHUNKHOUND_LLM_ANTHROPIC_THINKING_ENABLEDEnable extended thinking
CHUNKHOUND_LLM_ANTHROPIC_THINKING_MODEThinking mode: auto, off, manual, or adaptive
CHUNKHOUND_LLM_ANTHROPIC_THINKING_BUDGET_TOKENSManual-mode thinking budget (min 1024, default: 10000)
CHUNKHOUND_LLM_ANTHROPIC_THINKING_DISPLAYAdaptive-mode thinking text: summarized or omitted
CHUNKHOUND_LLM_ANTHROPIC_INTERLEAVED_THINKINGManual-mode interleaved thinking between tool calls
CHUNKHOUND_LLM_ANTHROPIC_EFFORTToken-usage effort: low, medium, high, xhigh, max
CHUNKHOUND_LLM_ANTHROPIC_TASK_BUDGET_TOKENSAdvisory agentic-loop token budget (beta)
CHUNKHOUND_LLM_ANTHROPIC_PROMPT_CACHINGSend cache_control for prompt caching
CHUNKHOUND_LLM_ANTHROPIC_CACHE_TTLPrompt-cache TTL (e.g. 1h)
CHUNKHOUND_LLM_ANTHROPIC_CONTEXT_MANAGEMENT_ENABLEDAutomatic clearing of tool results and thinking blocks (beta)
CHUNKHOUND_LLM_ANTHROPIC_CLEAR_THINKING_KEEP_TURNSThinking turns to keep when context management clears them
CHUNKHOUND_LLM_ANTHROPIC_CLEAR_TOOL_USES_TRIGGER_TOKENSInput-token threshold that triggers tool-result clearing
CHUNKHOUND_LLM_ANTHROPIC_CLEAR_TOOL_USES_KEEPNumber of recent tool-use pairs to keep after clearing
CHUNKHOUND_LLM_GEMINI_THINKING_LEVELGemini thinking depth (low, medium, high)
CHUNKHOUND_LLM_GEMINI_THINKING_BUDGETGemini fixed thinking token budget
CHUNKHOUND_INDEXING__EXCLUDE_MODEExclusion mode (combined, config_only, gitignore_only)
CHUNKHOUND_INDEXING__EXCLUDEGlob patterns to exclude from indexing
CHUNKHOUND_INDEXING__INCLUDEGlob patterns limiting which files are indexed
CHUNKHOUND_INDEXING__CLEANUPRemove orphaned DB records after indexing (default: true)
CHUNKHOUND_INDEXING__FORCE_REINDEXForce re-indexing of all files (default: false)
CHUNKHOUND_INDEXING__MAX_FILE_SIZE_MBSkip files larger than this (MB, default: 10)
CHUNKHOUND_INDEXING__CONFIG_FILE_SIZE_THRESHOLD_KBSkip structured config files larger than this (KB, default: 20)
CHUNKHOUND_INDEXING__PER_FILE_TIMEOUT_SECONDSPer-file parse timeout (default: 3.0)
CHUNKHOUND_INDEXING__PER_FILE_TIMEOUT_MIN_SIZE_KBOnly apply per-file timeout to files at least this large (KB, default: 128)
CHUNKHOUND_INDEXING__BATCH_SIZEFiles per parsing batch (default: 50)
CHUNKHOUND_INDEXING__DB_BATCH_SIZEChunks per database write batch (default: 100)
CHUNKHOUND_INDEXING__MAX_CONCURRENTMax concurrent parser workers (default: 5)
CHUNKHOUND_INDEXING__CHUNK_OVERLAPInternal chunk overlap (default: 50)
CHUNKHOUND_INDEXING__MIN_CHUNK_SIZEInternal min chunk size (default: 50)
CHUNKHOUND_INDEXING__INDEX_UNKNOWN_FILESIndex files with unrecognized extensions as plain text (default: false)
CHUNKHOUND_INDEXING__DETECT_EMBEDDED_SQLEnable embedded SQL detection (default: true)
CHUNKHOUND_INDEXING__DISCOVERY_BACKENDFile discovery backend: auto, python, git, git_only (default: auto)
CHUNKHOUND_INDEXING__GITIGNORE_BACKENDBackend for gitignore evaluation: python or libgit2 (default: python)
CHUNKHOUND_INDEXING__CHIGNORE_FILEChunkHound-specific ignore file name (default: .chignore)
CHUNKHOUND_INDEXING__GIT_PATHSPEC_CAPMax git pathspec entries (default: 128)
CHUNKHOUND_INDEXING__MTIME_EPSILON_SECONDSTolerance for file mtime comparison (seconds, default: 0.01)
CHUNKHOUND_INDEXING__PARALLEL_DISCOVERYEnable parallel directory traversal for large codebases (default: true)
CHUNKHOUND_INDEXING__MIN_DIRS_FOR_PARALLELMinimum top-level directories to activate parallel discovery (default: 4)
CHUNKHOUND_INDEXING__MAX_DISCOVERY_WORKERSMaximum worker processes for parallel discovery (default: 16)
CHUNKHOUND_INDEXING__WORKSPACE_GITIGNORE_OVERLAYApply CH root .gitignore as global overlay across repos (default: false)
CHUNKHOUND_INDEXING__WORKSPACE_GITIGNORE_NONREPOUse CH root .gitignore only for non-repo paths (default: true)
CHUNKHOUND_INDEXING__REALTIME_BACKENDFilesystem monitoring backend: watchman, watchdog, or polling
CHUNKHOUND_DB_EXECUTE_TIMEOUTLegacy alias for CHUNKHOUND_DATABASE__EXECUTE_TIMEOUT_SECONDS
CHUNKHOUND_YAML_ENGINEYAML parser engine (rapid or tree)
CHUNKHOUND_CONFIG_FILEPath to config file (alternative to --config)
CHUNKHOUND_WEBSEARCH_TIMEOUT_SECONDSWeb search subprocess timeout in seconds (default: 600)
CHUNKHOUND_DEBUGEnable debug logging
VOYAGE_API_KEYFallback API key for VoyageAI provider

Advanced routing

The homepage configurator emits the 30-second onboarding shape. Real enterprise deployments often need to hit Azure, a self-hosted endpoint, or an LLM proxy. Below is what ChunkHound actually wires through, and what it doesn’t.

TLS verification for custom endpoints

ssl_verify is explicit now. ChunkHound does not disable certificate verification automatically.

  • embedding.ssl_verify only affects requests sent to an explicit embedding.base_url.
  • embedding.rerank_ssl_verify only affects rerank requests and overrides inherited ssl_verify when set.
  • llm.ssl_verify only affects requests sent to an explicit llm.base_url.
  • If base_url is unset, ssl_verify is ignored for security.
  • If rerank_url is unset, rerank_ssl_verify is ignored.
  • Prefer a proper CA trust chain when possible. Use false only for local endpoints or trusted internal networks with self-signed/private certificates.

Azure OpenAI (embeddings)

ChunkHound’s OpenAI embedding provider speaks Azure OpenAI natively. Supply the four Azure fields and omit base_url — the two are mutually exclusive.

{
  "embedding": {
    "provider": "openai",
    "model": "text-embedding-3-small",
    "api_key": "<YOUR_AZURE_KEY>",
    "azure_endpoint": "https://<resource>.openai.azure.com",
    "api_version": "2024-02-01",
    "azure_deployment": "<your-deployment-name>"
  }
}

LLM-side Azure OpenAI is not supported yet — the llm section has no Azure fields. Use a proxy (see below) if you need to route LLM traffic through Azure.

VoyageAI on Azure ML / AI Foundry

VoyageAI models are available on the Azure Marketplace and in Microsoft Foundry. ChunkHound can target an Azure-hosted Voyage deployment via base_url:

{
  "embedding": {
    "provider": "voyageai",
    "model": "voyage-3.5",
    "api_key": "<YOUR_AZURE_VOYAGE_KEY>",
    "base_url": "https://<your-resource>.services.ai.azure.com/models",
    "ssl_verify": true,
    "rerank_url": "https://<your-rerank-endpoint>/rerank",
    "rerank_ssl_verify": true,
    "rerank_format": "tei"
  }
}

Caveats:

  • Native Voyage API required. The Azure deployment must expose /v1/embeddings with the native Voyage shape (true for Voyage marketplace listings; verify your specific deployment).
  • Bundled reranker unavailable. VoyageAI’s rerank-* models are not accessible through a custom base_url — the embedding endpoint doesn’t expose /rerank. Run a separate reranker and point rerank_url at it. vLLM with Qwen/Qwen3-Reranker-0.6B is a drop-in option:
    vllm serve Qwen/Qwen3-Reranker-0.6B --task score --port 8000
  • TLS disablement is primarily for the HTTP reranker path. The separate rerank_url path respects ssl_verify / rerank_ssl_verify. For the VoyageAI SDK path, prefer trusted CA configuration such as REQUESTS_CA_BUNDLE.
  • Concurrency throttled to 1 by default when base_url is set, to respect Azure serverless rate limits. Override via max_concurrent_batches if your SKU permits.
  • api_key still required. The validator doesn’t enforce it when base_url is present, but Azure-hosted endpoints still need their own key — supply it.

LLM via proxy (Anthropic, OpenAI, Grok, DeepSeek, OpenRouter, OrcaRouter, Requesty)

The Anthropic, OpenAI, Grok, DeepSeek, OpenRouter, OrcaRouter, and Requesty LLM providers all forward base_url to their SDK. Point them at a gateway like LiteLLM or OrcaRouter to centralize auth, logging, and rate limiting:

{
  "llm": {
    "provider": "anthropic",
    "model": "claude-sonnet-4-5-20250929",
    "api_key": "<YOUR_GATEWAY_KEY>",
    "base_url": "https://your-gateway.example.com",
    "ssl_verify": true
  }
}

The gateway must preserve each provider’s native request/response shape — ChunkHound uses the vendor SDKs, not a generic HTTP client.

Local OpenAI-compatible servers (Ollama, vLLM)

Local inference servers that speak the OpenAI API work via provider: "openai" with base_url pointing at the local endpoint. No api_key is needed for servers that don’t enforce auth, but you must set an explicit model.

Ollama

Ollama provides embeddings, reranking, and LLM inference in a single process. Pull the models you need, then point ChunkHound at the Ollama endpoint:

# Embedding + reranker models
ollama pull qwen3-embedding && ollama pull qwen3-reranker

# LLM — pick one
ollama pull qwen3-coder:30b
ollama pull gemma4:27b

Embedding and reranker config (.chunkhound.json):

{
  "embedding": {
    "provider": "openai",
    "model": "qwen3-embedding",
    "base_url": "http://localhost:11434/v1",
    "ssl_verify": false,
    "rerank_model": "qwen3-reranker",
    "rerank_format": "cohere"
  }
}

No rerank_url is needed — it is auto-derived from base_url.

LLM config:

Migration note: Do not set llm.provider to "ollama". ChunkHound treats Ollama as an OpenAI-compatible endpoint, so use provider: "openai" with the Ollama base_url and an explicit model.

{
  "llm": {
    "provider": "openai",
    "model": "qwen3-coder:30b",
    "base_url": "http://localhost:11434/v1",
    "ssl_verify": false
  }
}

Use whichever model you pulled in llm.model. For example, set "model": "gemma4:27b" if you want the Gemma 4 path instead of Qwen. ChunkHound does not infer a local default model from base_url.

If your embeddings stay on the official provider but reranking goes to a local HTTPS service with a self-signed certificate, override the reranker only:

{
  "embedding": {
    "provider": "openai",
    "model": "text-embedding-3-small",
    "api_key": "<YOUR_OPENAI_KEY>",
    "rerank_model": "Qwen/Qwen3-Reranker-0.6B",
    "rerank_url": "https://localhost:8001/rerank",
    "rerank_ssl_verify": false,
    "rerank_format": "tei"
  }
}

vLLM

vLLM gives you dedicated processes per model, which is better for throughput and lets you serve HuggingFace model IDs directly. When embeddings and reranking are served from the same OpenAI-compatible endpoint, ChunkHound infers the reranker path from base_url just like it does for Ollama:

# Embedding + reranker server
vllm serve Qwen/Qwen3-Embedding-0.6B --port 8000

# LLM server
vllm serve Qwen/Qwen3-Coder-30B-A3B-Instruct --port 11434

Embedding and reranker config (.chunkhound.json):

{
  "embedding": {
    "provider": "openai",
    "model": "Qwen/Qwen3-Embedding-0.6B",
    "base_url": "http://localhost:8000/v1",
    "rerank_model": "Qwen/Qwen3-Reranker-0.6B",
    "rerank_format": "cohere"
  }
}

No rerank_url is needed when the reranker lives behind the same OpenAI-compatible endpoint. ChunkHound auto-derives /rerank from base_url.

If you split embeddings and reranking across different services, keep base_url pointed at the embedding server and set rerank_url explicitly:

{
  "embedding": {
    "provider": "openai",
    "model": "Qwen/Qwen3-Embedding-0.6B",
    "base_url": "http://localhost:8025/v1",
    "rerank_model": "Qwen/Qwen3-Reranker-0.6B",
    "rerank_url": "http://localhost:8000/rerank",
    "rerank_format": "cohere"
  }
}

LLM config:

{
  "llm": {
    "provider": "openai",
    "model": "Qwen/Qwen3-Coder-30B-A3B-Instruct",
    "base_url": "http://localhost:11434/v1"
  }
}

Ollama vs vLLM: Ollama is simpler — one process, one command per model. vLLM is better for throughput and gives you full control over each serving process. Both work equally well with ChunkHound as long as llm.model is set explicitly.

The websearch tool searches the web via DuckDuckGo, fetches the top pages, indexes the fetched content in memory, and runs the same deep research pipeline used for local code search. It is available as an MCP tool and as chunkhound websearch.

Requirements

The web search tool requires all three provider capabilities to be configured:

  • Embedding provider — e.g. embedding.provider: "voyageai" or "openai"
  • LLM provider — for query expansion and answer synthesis
  • Reranking — embedding.rerank_model must be set for relevance-aware multi-hop search

If any of these are missing, the MCP websearch tool is not registered (capability gating) and the CLI command will fail.

Parameters

ParameterTypeDefaultDescription
querystringrequiredNatural-language or keyword query sent to DuckDuckGo
--limit / limitint30Max results to fetch (1–100). CLI uses --limit, MCP uses limit.

Environment Variables

VariableDescription
CHUNKHOUND_WEBSEARCH_TIMEOUT_SECONDSWall-clock timeout (seconds) for the research subprocess. Default: 600. Also returned for malformed values.

Fixed Constants

ConstantValueDescription
WEBSEARCH_LIMIT_MAX100Upper bound for the --limit / limit parameter

Browser Dependency

The fetch path uses zendriver (v0.15.3, core dependency — no extra install needed) to drive the system-installed Google Chrome for rich page rendering. Chrome >=124 is required. If Chrome is not found or too old, fetches fall back to urllib (less capable — may miss JS-rendered content and cannot fetch some PDFs).

Research Config Linkage

The web search tool delegates to the same deep research pipeline as code_research. All settings in the Research Configuration section apply: algorithm, multi_hop_time_limit, relevance_threshold, query_expansion_enabled, target_tokens, etc.

Fetch URL

The fetchurl tool fetches a single URL, extracts its content, and returns a focused Markdown answer via one LLM call (short pages) or a rerank+elbow pipeline over page chunks (long pages with a query). It is available as an MCP tool and as chunkhound fetchurl. Fetches use the same zendriver + system Chrome transport as Web Search with the same urllib fallback — see that section’s Browser Dependency note for Chrome version requirements and fallback behavior.

Requirements

The fetch URL tool requires two provider capabilities to be configured:

  • LLM provider — for the extraction/answer call
  • Reranker-capable embedding provider — VoyageAI (SDK), or a Cohere/TEI HTTP reranker. rerank_model is required for the VoyageAI SDK and Cohere paths; TEI needs rerank_format=tei + rerank_url (no rerank_model).

If either is missing, the MCP fetchurl tool is not registered (capability gating hides it from tools/list) and the CLI command exits 1 with an explicit error message.

Parameters

ParameterTypeDefaultDescription
urlstringrequiredAbsolute http:// or https:// URL. Non-http(s) schemes are rejected, as are hosts resolving to loopback / private / link-local / reserved / multicast / unspecified addresses.
query / --query / -qstring""Optional question. When set, focuses extraction and enables the rerank+elbow path on pages exceeding fetchurl.rerank_threshold_tokens.

Environment Variables

VariableDescription
CHUNKHOUND_FETCHURL_RERANK_THRESHOLD_TOKENSOverrides fetchurl.rerank_threshold_tokens.
CHUNKHOUND_FETCHURL_TRUNCATE_TOKENSOverrides fetchurl.truncate_tokens.
CHUNKHOUND_FETCHURL_MAX_RETRIESOverrides fetchurl.max_retries.

Configuration File

{
  "fetchurl": {
    "rerank_threshold_tokens": 15000,
    "truncate_tokens": 15000,
    "max_retries": 3
  }
}
KeyTypeDefaultDescription
rerank_threshold_tokensint (≥1)15000Estimated token count above which the chunk-rerank path (chunk + rerank + elbow filter) is used instead of the truncate path (token-truncate + single LLM call). Only applies when query is set — without a query, the truncate path is always used regardless of page size. Tokens are estimated at 4 chars/token.
truncate_tokensint (≥1)15000Token cap applied to the truncate-option input before the LLM call. Content is sliced to truncate_tokens × 4 characters.
max_retriesint (1–10)3Fetch attempts including the first. Uses exponential backoff with full jitter capped at 8s. Browser-transport death consumes an attempt slot and downgrades remaining attempts to urllib.

CLI vs MCP: The three knobs above are exposed as --fetchurl-* flags on the CLI (chunkhound fetchurl). The MCP fetchurl tool accepts only url and query — knob overrides must come from config or CHUNKHOUND_FETCHURL_* env vars.