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.jsonis live:16 on a packaged/Docker install,/app/datais a named volume — the server reads the settings file inside the volume, not thedata/settings.jsonsitting in the package directory on the host. Edit the live one via the web UI (System screen) ordocker 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.
-
Vendor change reference CN-84. ↩
-
Vendor change reference CN-174. ↩
-
Vendor change reference CN-746. ↩
-
Vendor change reference CN-748. ↩
-
Vendor change reference CN-233. ↩
-
Vendor change reference CN-234. ↩
-
Vendor change reference CN-261. ↩
-
Vendor change reference CN-288. ↩
-
Vendor change reference CN-289. ↩
-
Vendor change reference CN-349. ↩
-
Vendor change reference CN-412. ↩
-
Vendor change reference CN-421. ↩
-
Vendor change reference CN-549. ↩
-
Vendor change reference CN-561. ↩
-
Vendor change reference CN-584. ↩
-
Vendor change reference CN-550. ↩
-
Vendor change reference CN-609. ↩