Skip to content

Configuration

CodeNavi reads two files. .env (next to docker-compose.yml in your install directory) holds everything the container needs at boot — paths, ports, the auth token, GPU overrides, provider keys. data/settings.json holds runtime settings — index exclusions, scanner policy, crawler limits — most of them also editable on the web UI Settings screen. LLM and embedding providers have their own page: Providers.

Set these in .env (read by docker-compose). For runtime settings, copy data/settings.json.example to data/settings.json as a starting point — it documents the supported settings with _comment_* keys (LLM provider/key settings live in .env — see Providers).

Which settings.json is live:16 on a packaged/Docker install, /app/data is a named volume — the server reads the settings file inside the volume, not the data/settings.json sitting in the package directory on the host. Edit the live one via the web UI (System screen) or docker exec -it <container> vi /app/data/settings.json. Boot-bound keys (INCLUDE_EXTENSIONS, EXCLUDE_DIRS, MAX_FILE_SIZE, …) additionally need a container restart + reindex; scanner keys hot-reload. If you edit after the server started, the next reindex prints a CONFIG WARNING naming exactly this.15

Environment variables (.env)

Core

Variable Default Description
CODEBASE_PATH (required) Absolute path to the codebase directory to index (single codebase)
CODEBASE_CONFIG (optional) JSON map of name→path pairs for multi-codebase mode (overrides CODEBASE_PATH). Example: {"myapp":"/var/www/myapp","api":"/var/www/api"}
CODENAVI_PORT 8765 HTTP port for the MCP server + web UI (/ui)
CODENAVI_METRICS_PORT 9091 Prometheus metrics endpoint port
CODENAVI_AUTH_TOKEN (empty) Bearer token for authentication. When set, all MCP requests require Authorization: Bearer <token>. When empty, the server runs without auth.
CODENAVI_STATELESS_HTTP true MCP transport session mode.12 Default true (stateless): every request is independent, no server-side session state — robust for a self-hosted server and immune to "session not found" on restart. Set false for stateful sessions, which lets query logs record the exact calling client (the initialize handshake's clientInfo name/version, e.g. Cursor/1.x) instead of falling back to the HTTP User-Agent. Useful for integration QA where several agents share one server and you need to tell them apart. Do not enable in production unless you accept that a server restart invalidates in-flight client sessions (clients must re-initialize, and some surface an error first). Restart to apply.
HF_TOKEN (empty) Hugging Face token for model downloads (may be needed for gated models)
DOCS_DIR (empty) Absolute path to external documentation directory (PDF/DOCX files). Mounted read-only at /app/external-docs. On a multi-codebase server, use DOCS_DIR/<codebase>/ subfolders so each codebase ingests only its own docs (flat layout is a legacy fallback).
DOCS_ENABLED true Enable/disable doc ingestion.
CODENAVI_STALENESS_THRESHOLD_SEC 3600 Index age (seconds) past which the always-on freshness envelope recommends a reindex.13 Lower it on a fast-moving repo so the agent reindexes sooner; raise it on a stable/archived codebase so the hint isn't token-costing noise on every query. Also settable in settings.json. Restart to apply.
CODENAVI_STRONG_STALENESS_SEC 86400 Index age (seconds) past which the reindex recommendation escalates its wording to "likely needed".13 Also settable in settings.json. Restart to apply.

Tool surface

The full MCP tools/list schema (42 tools with the crawler on) is ~13k tokens, injected once per conversation into the agent's prompt. For token-sensitive clients or short sessions you can advertise a lean core instead8 — the rest stay available on demand.

Variable Default Description
CODENAVI_TOOL_PROFILE lean lean (default since 1.3.23) advertises only an 11-tool core — the search/browse/read loop (semantic_search, file_search, grep_search, symbol_search, list_directory, read_file, codebase_overview, list_codebases) plus the everyday index-ops tools (reindex_incremental, reindex_status, index_stats) — and a reveal_tools() meta-tool — ~−57% smaller tools/list; every other tool loads on demand via reveal_tools(). full advertises every tool up front (the pre-1.3.23 default).
CODENAVI_TOOL_PROFILE_CORE (built-in 11) Comma-separated override of which tools form the lean core.

In lean mode the specialized tools (relations, reindex, feedback/analytics, crawler, chat, backup, docs, notes) aren't gone — call reveal_tools() with no arguments for a grouped catalog, then reveal_tools(group="relations") or reveal_tools(names="find_callers,diff_impact") to load them into the live session (the server re-registers them and emits tools/list_changed so the client re-fetches). The reveal is server-wide and resets to lean when the container restarts.

When to use lean vs full. tools/list is sent once per conversation and sits in the cacheable prompt prefix (~5-min TTL), so its cost is mostly paid when a session starts cold. Whether lean saves real tokens depends on how cold your sessions run:

Usage pattern lean payoff
One long, warm conversation Marginal — caching already serves the schema at ~0.1× after the first turn
Many short / fresh sessions (cold cache) ~6,400 tokens saved per session (−61% of the schema) — compounds fast
Multi-agent fan-out (many fresh sub-sessions) Largest win — every spawned session is a fresh cold injection
Clients that don't cache tool definitions Largest win — the full schema is re-sent every turn
Tight context budgets Frees ~6k tokens of the window regardless of cost

Rough scale: one heavy developer (~20 fresh sessions/day) saves on the order of a few million input-token-equivalents/month; a multi-agent fleet that opens many cold short sessions can save 10× that or more. Tradeoff: in lean, a session that needs an advanced tool pays one reveal_tools() round-trip first — so lean is a clean win for search/browse/read work and roughly break-even for tool-heavy sessions.

Rule of thumb: keep full for everyday single-developer use; switch to lean for fleets, CI/automation, multi-agent orchestration, and any non-caching client. It is opt-in — the default costs nothing extra.

Platform (set by setup.sh)

Variable Default Description
COMPOSE_FILE (not set) Controls compose file merging. GPU: docker-compose.yml:docker-compose.gpu.yml. GPU under podman: add :docker-compose.podman-nvidia.yml (podman ignores the gpu overlay's runtime: nvidia; the extra overlay requests the card as a CDI device — see Podman). CPU/Mac: not set (uses base only).
TORCH_INDEX (source-build only) The GA image is prebuilt on the universal cu128 / torch 2.7.1 wheel (arch sm_75–sm_120, driver ≥ 570.65), so this has no effect on the shipped image. For a from-source build, setup.sh auto-selects cu128 (Blackwell) or cu124 (older GPUs), or .../whl/cpu for Mac/CPU.
TORCH_PACKAGE (source-build only) See TORCH_INDEX. The GA image ships torch==2.7.1.

GPU autotune overrides (optional)

CodeNavi auto-detects VRAM at startup and picks an encode batch size + reranker device that fit the card61011 (8–10 GiB → batch 2 int8-resident + reranker on CPU; 11–12 GiB → batch 1 fp16 + reranker on CPU; 16 GiB → batch 4 + reranker on CPU; ≥17 GiB → batch 16 + reranker on CUDA). On an 11–12 GiB card the fp16-resident 4B embedder plus a batch‑2 activation OOMs on dense codebases, so the small tier uses batch 1 (~2× slower but reliable). Override only if you know what you're doing.

Variable Default Description
CODENAVI_VRAM_BUDGET_GIB (unset) Soft cap.7 Tell CodeNavi to autotune as if the card only had N GiB. Use this when you share the GPU with other workloads (e.g. Ollama, a generative model, training) and want CodeNavi to stay out of the way at the cost of indexing speed. Example: on a 32 GiB card running Ollama, set CODENAVI_VRAM_BUDGET_GIB=11 to force the 11–12 GiB tier (batch 1 + reranker on CPU). Soft cap only — changes autotune decisions, does not hard-limit PyTorch allocation; concurrent VRAM spikes from the other workload can still cause OOM.
CODENAVI_GPU_DEVICE 0 (AMD) / all-visible (NVIDIA) Multi-GPU hosts: which card CodeNavi uses (index). CodeNavi runs on exactly one GPU; a second instance can take another card by setting a different index in its own .env. Restart to apply.17 On Windows (Docker Desktop) honoured from package 1.3.43 (index as nvidia-smi -L prints it). On podman + NVIDIA the index must exist in the CDI spec: /etc/cdi/nvidia.yaml is a static snapshot, so a card added after it was generated is unusable until you re-run sudo nvidia-ctk cdi generate --output=/etc/cdi/nvidia.yaml (see docs/podman.md). Docker is unaffected.
ENCODE_BATCH_SIZE (autotuned) Override the autotuned encode batch size. Wins over CODENAVI_VRAM_BUDGET_GIB.
RERANKER_DEVICE (autotuned) Force the reranker onto cuda or cpu. Auto:19 the GPU whenever the embedder is on it; on cards under 17 GiB it steps to the CPU only while a reindex is encoding, then returns to the GPU on the next query. Wins over CODENAVI_VRAM_BUDGET_GIB. Installs made before 1.3.24 carry a RERANKER_DEVICE=cpu line in .env from the old autotune — delete it to get the faster GPU rerank.

Stored-index integrity

Variable Default Description
INDEX_INTEGRITY_CHECK_INTERVAL 21600 Seconds between checks of the stored index against the fingerprint recorded at its last reindex (index/integrity_<codebase>.json).4 The result is index_integrity in the authenticated /health body and the codenavi_index_integrity gauge; a mismatch is a warning, never a refusal — run a full reindex to rebuild and re-record. The check also runs once after start-up; 0 disables only the timer.

Throttling

Every HTTP request — the MCP transport, the console API and the admin routes alike — passes two sliding windows before the server does any work.3 A request that overflows either answers 429 Too Many Requests with a Retry-After header and a JSON body naming the reason. /health, /.well-known/* and the static console files are never throttled, so the plugin's start-up probe and the statusline keep working while a client is refused. Loopback is not exempt: a coding agent stuck in a loop on the same host is the case this exists for. The web console and an agent on the same host land in separate buckets because the bucket key includes the user-agent. Read once at start; restart to apply.

Variable Default Description
THROTTLE_ENABLED true Set to false to switch both windows off.
THROTTLE_MAX_REQUESTS 200 Requests one client (address + user-agent) may make per window before it gets 429. Batches of read_file count one each. Every agent session on one machine shares a bucket, and so does a whole team behind a NAT or a proxy on another host unless that proxy is in THROTTLE_TRUSTED_PROXIES — size the number for the busiest shared client, not for one agent.
THROTTLE_WINDOW_SECONDS 10 Length of the request window.
THROTTLE_AUTH_MAX_FAILURES 20 Distinct rejected bearer tokens one address may present per window. Past it, every request from that address is refused until the window frees — including a correct token. Requests without an Authorization header do not count, and the same wrong token re-sent counts once per window, so a console tab still holding a rotated token cannot lock the host out.
THROTTLE_AUTH_WINDOW_SECONDS 60 Length of the rejected-credential window and of the lockout.
THROTTLE_TRUSTED_PROXIES 127.0.0.0/8,::1 Comma-separated addresses or CIDRs whose X-Forwarded-For is believed. Behind a reverse proxy on another host, add that host here or every client shares the proxy's buckets. Any other peer's X-Forwarded-For is ignored, so a direct client cannot rotate itself out of a lockout.

The /metrics counter codenavi_throttled_requests_total{reason="auth"|"load"} counts refusals; the first refusal per client and reason in a window also leaves a WARNING line in the server log with the client address, never the token.

Crawler & feature switches

Variable Default Description
CRAWLER_ENABLED false Off by default.2 Set to true to enable the crawler tools, web_chunks indexing, and the Crawler UI screen. Setup, limits and login sessions: Deploy crawler.
CODENAVI_GRAPH_CROSS_LANGUAGE false Call-graph tools (find_callers, impact_analysis, diff_impact, graph_neighborhood, hub metrics) only follow a call edge when caller and callee share a language family (JS/TS are one). Set to true to restore cross-language name matching.18
WIKI_SEARCH_BLEND true Compiled Docs Wiki pages (title + lead, embedded with the content model at compile time) blended into semantic_search as labeled advisory results that point at wiki_page(slug) and the source docs. Shares the advisory result slots with notes (1 at the default limit, 2 above), shown on top of limit — never in place of a code result. No LLM call at query time. Set to false to keep the wiki browse-only.
CODENAVI_NOTES_ENABLED true Investigation notes:20 save_note/search_notes/list_notes/delete_note plus fresh notes blended into semantic_search as labeled advisory results. Set to false to hide the tools and disable the blended read path entirely.
AUTO_ALIASES false Auto-generated per-codebase query aliases (Tier 3)21. Off by default since 1.3.26: the mechanical generator measured a net loss on large repositories, so it neither runs on full reindex nor applies an existing aliases_{codebase}.json. Hand-curated aliases (aliases.json, aliases_{codebase}_manual.json, the Search screen's alias editor) always apply. Set to true (settings.json, or env CODENAVI_AUTO_ALIASES) and restart to opt in; AUTO_ALIAS_GEN_SKIP then excludes single codebases.
EMBED_SKIP_DIRS ["public_html", "site", "resources", "templates", "lib", "models", "assets"] Directory names dropped from the embedding text header, not from the index. Each chunk is embedded with a short path-context header (up to 3 directory names); listing a generic wrapper directory here keeps low-signal words like lib or templates out of that header so they don't dilute the vector. Matching is per path segment, case-insensitive. The list replaces the default, so repeat the defaults you still want. To keep a folder out of the index entirely, use EXCLUDE_DIRS instead — see Excluding folders from the index. Restart to apply.
CRAWL_DB_PATH /app/crawl_runs.db Path to the crawl run history SQLite database inside the container
CODENAVI_CRAWLER_SSRF_GUARD 1 Resolves each hostname and refuses private, loopback, and cloud-metadata addresses. env=local permits loopback and RFC1918 (that is what local crawling means), but cloud-metadata ranges (169.254.0.0/16, fe80::/10, fd00:ec2::/32) are refused in every env. Set to 0 to disable — do not do this in production, it re-opens fetch-any-internal-address.

The crawler's destructive-action guard needs no configuration: the block floor is compiled into crawler/safety_scanner.py and is always in force. data/crawl_safety.json is optional and only extends or relaxes it — see Crawler safety config. Copy data/crawl_safety.json.example to start one.

Event hooks (optional)

Variable Default Description
HOOK_URL (empty) Webhook URL to POST events to (reindex, crawl, chat, etc.)
HOOK_SCRIPT_DIR (empty) Directory of <event>.sh scripts to execute on events
HOOK_TIMEOUT_SEC 5 Per-request timeout (seconds) for HOOK_URL webhook POSTs

See webhooks.md for the full event reference, payload shapes, the airgap-native script channel, and ready-to-use examples in examples/hooks/ in your CodeNavi package.

Internal (set in docker-compose.yml, not normally changed)

Variable Default Description
CODEBASE_PATH /codebase Mount point inside the container
CODENAVI_HOST 127.0.0.1 Host-side interface Docker publishes the port on — not the in-container bind (that's CODENAVI_BIND, default 0.0.0.01). Set CODENAVI_HOST=0.0.0.0 for LAN/remote access. Don't conflate the two.
HF_HOME /app/hf-cache Model cache directory
JINA_LOCK_FILE /tmp/jina-lock/codenavi_jina.lock GPU encoding mutex
TRANSFORMERS_OFFLINE 1 Skip network after initial model download

Multi-codebase mode

By default, CodeNavi indexes a single codebase from CODEBASE_PATH. To index multiple projects in one instance, use CODEBASE_CONFIG:

# In .env:
CODEBASE_CONFIG={"laravel":"/var/www/laravel","wordpress":"/var/www/wordpress","api":"/var/www/api"}

Each codebase gets its own LanceDB tables (e.g., laravel_code_chunks, wordpress_code_chunks). All MCP tools accept an optional codebase parameter to scope queries:

semantic_search("authentication", codebase="laravel")   # search only Laravel
semantic_search("authentication")                        # search all codebases
reindex(codebase="api")                                  # reindex only the API project
list_codebases()                                         # show all codebases with chunk counts

The /health endpoint shows per-codebase stats when multiple codebases are configured.

Notes: - Codebases are reindexed sequentially (not in parallel) to avoid GPU contention. - Search results include a <codebase> tag so you know which project each result came from. - Existing single-codebase deployments are fully backward compatible — no migration needed.

Runtime settings (data/settings.json)

Excluding folders from the index

To keep a folder out of the index — asset dumps, generated output, a docs subtree that dilutes search results — add its path to EXCLUDE_DIRS in data/settings.json, then restart and reindex:

{
  "EXCLUDE_DIRS": ["data/pdfs", "data/images", "legacy/generated"]
}
docker compose restart          # EXCLUDE_DIRS is boot-bound
./reindex.sh                    # incremental is enough — see below

On a packaged install, edit the live settings file inside the container's data volume, not the copy in the package directory:16 docker exec -it codenavi vi /app/data/settings.json (or the web UI's System screen).

Setting What it does
EXCLUDE_DIRS Paths never scanned. Merged additively with ~50 built-in defaults (node_modules, vendor, .git, dist, build, cache, …) — your list adds to them, it does not replace them.9 A single name (cache) matches a directory of that name at any depth; a path (data/pdfs) matches those segments consecutively at any depth.
EXCLUDE_DIRS_DROP Un-excludes a built-in default — use this if your source really does live in build/ or vendor/.
INDEX_INCLUDE_OVERRIDES Path prefixes indexed no matter what: bypasses both EXCLUDE_DIRS and the .gitignore layer. Use it to re-include one subtree of an otherwise excluded folder.

You may not need any of these — CodeNavi already honours the repository's own .gitignore (and .dockerignore-style tool ignore files) by default, so a folder that is gitignored is already out of the index.9 Set RESPECT_GITIGNORE to false to turn that off.

Multi-codebase servers: an unprefixed entry applies to every codebase — a bare "docs" blacks out docs/ everywhere. Prefix it to scope it: "mycodebase:docs/internal".14 The same syntax works in EXCLUDE_DIRS_DROP and INDEX_INCLUDE_OVERRIDES. A prefix that looks like a codebase name but is not a configured one logs a warning rather than silently matching nothing.

An incremental reindex is enough. It rebuilds the table from a fresh scan, so chunks from newly-excluded files are dropped; --full is only needed when you also change INCLUDE_EXTENSIONS or chunking settings.

Verify it took effect — ask the server's own filter, per file:

docker exec -i codenavi python3 -c "
from pathlib import Path
import config, indexing
for rel in ['data/pdfs/notes.md', 'src/main.py']:
    print(rel, indexing.should_include(Path(config.CODEBASE_ROOT) / rel))"

index_stats also reports the skip ledger — how many files were dropped and why. Note that extension and size rules are checked before EXCLUDE_DIRS, so a PDF in an excluded folder is still counted under "skipped by extension"; it is out of the index either way.

First-time setup can guess for you. codenavi init <path> scans a codebase and writes a starter data/settings.json with EXCLUDE_DIRS pre-seeded: the usual junk (node_modules, vendor, .git, dist, build, target, …) plus any directory holding more than 200 files whose path looks like an asset folder (images, photos, uploads, fonts, icons, static, media, assets/images, public/images). It prints an Auto-excluded: line naming what it added beyond the defaults — review it, then reindex. Two limits worth knowing: the 200-file floor means a small asset folder is not caught (a folder of 26 PDFs is under it), and the command writes a fresh settings file — it refuses to overwrite an existing one without --force, so it is a bootstrap tool, not a way to edit a running install.

Not the same as exclude_paths — that is a search-time filter an agent passes per query. It hides results but the content is still indexed and still competes for ranking. Index-time exclusion is what changes what the index contains.

Other settings

Key settings beyond the defaults in settings.json.example:

Setting Default Description
RERANKER_MODEL ms-marco Cross-encoder reranker (Apache ms-marco), or none to disable.
FEEDBACK_DEMOTION_THRESHOLD -2 Net rating at which a chunk is demoted in search results. Lower = more lenient.
CONCURRENCY {} (4) Per-env max parallel crawl requests, e.g. {"prod": 2}.
REQUEST_DELAY_MS {} (0) Per-env delay before each crawl request, e.g. {"prod": 300}.
CRAWL_MAX_RPS {} (off) Per-env hard requests/second cap (sliding window over the target host) — stays under a site's per-IP rate limit. e.g. {"prod": 4}. Short bursts tolerated, sustained load throttled.
CRAWL_MAX_POSTS_PER_MIN {} (off) Per-env cap on form-submission POSTs/minute — stays under a "create endpoint" budget. e.g. {"prod": 8}.
CRAWLER_EXCLUDE_URL_PATTERNS {} Per-env list of regexes; a URL matching one is never fetched. Prunes faceted navigation and pagination from a crawl, e.g. {"prod": ["/search-filter/", "[?&](pag|page)="]}. A regex that fails to compile is skipped.
CRAWLER_LOGIN_URL_PATTERNS (site-specific defaults) Substrings that mark a URL as a login page. A crawl fetch whose final URL contains one, when the original did not, is recorded as a session-expiry redirect instead of a page. Set it for your site — see Crawl behind a login.5
CRAWLER_AUTH_PROBE_PATHS (site-specific defaults) Per-level path (member, admin, affiliate) appended to the env base URL to confirm a cookie session is alive before a crawl.5

Staying under a rate-limited target (e.g. ~5 req/s per IP + 10 posts/min on create): set "CRAWL_MAX_RPS": {"prod": 4} and "CRAWL_MAX_POSTS_PER_MIN": {"prod": 8} (headroom under the limits). The crawler also honours 429 / Retry-After automatically — on a throttle response it sleeps the advertised delay (capped at 60s) and retries instead of hammering. These apply on the next crawl run (the crawler is a fresh subprocess); no server restart needed.


  1. Vendor change reference CN-84. ↩

  2. Vendor change reference CN-174. ↩

  3. Vendor change reference CN-746. ↩

  4. Vendor change reference CN-748. ↩

  5. Vendor change reference CN-690. ↩↩

  6. Vendor change reference CN-233. ↩

  7. Vendor change reference CN-234. ↩

  8. Vendor change reference CN-261. ↩

  9. Vendor change reference CN-268. ↩↩

  10. Vendor change reference CN-288. ↩

  11. Vendor change reference CN-289. ↩

  12. Vendor change reference CN-349. ↩

  13. Vendor change reference CN-351. ↩↩

  14. Vendor change reference CN-412. ↩

  15. Vendor change reference CN-421. ↩

  16. Vendor change reference CN-423. ↩↩

  17. Vendor change reference CN-549. ↩

  18. Vendor change reference CN-561. ↩

  19. Vendor change reference CN-584. ↩

  20. Vendor change reference CN-550. ↩

  21. Vendor change reference CN-609. ↩