force=true re-index produces wildly inconsistent garbage file/chunk counts

Author: BottaniCalsCreated Jul 28, 2026Updated Jul 28, 2026

Bug description

index_codebase with force=true produces wildly inconsistent garbage results — each call returns a different, nonsensical file/chunk count that does not reflect the actual content of the codebase being indexed. The clear_index + index_codebase (no force) path on the same codebase consistently produces the correct count.

This is NOT the same bug as #295 (which was about snapshot 0/0 corruption causing an infinite reindex loop, fixed in v0.1.7). The bug here is that force=true produces incorrect indexing results — different file/chunk counts on every run, with the indexed content being junk (.venv/, .pytest_cache/, __pycache__/, .egg-info/, etc.) that the codebase's .gitignore explicitly excludes.

Reproduction steps

I ran this on the media-cli codebase (a small Python project, 57 git-tracked files: 31 .py + 18 .md + Makefile + pyproject.toml + scripts + tests + arr_cli/ package + .specs/ docs).

The codebase's .gitignore explicitly excludes:

  • .venv/, venv/, env/, ENV/
  • .pytest_cache/, .coverage, .mypy_cache/, .ruff_cache/, .tox/
  • __pycache__/, *.py[cod], *$py.class
  • *.egg-info/, *.egg
  • build/, dist/, .eggs/
  • .vscode/, .idea/, .DS_Store

Test sequence:

  1. Clear + default re-index: 34 files / 663 chunks (correct — matches the 31 .py + other source files that should be indexed, ~19 chunks per file because of substantial code content)
  2. index_codebase(force=true) run 1: 600 files / 600 chunks (incorrect — file count = chunk count = 1 chunk per file = trivial noise)
  3. index_codebase(force=true) run 2: 200 files / 200 chunks (different again, same 1:1 chunk-per-file ratio)
  4. index_codebase(force=true) run 3: 663 files / 663 chunks (different again)
  5. index_codebase(force=true) run 4: 400 files / 400 chunks (different again)
  6. Clear + default re-index (sanity check): 34 files / 663 chunks (back to the correct count)

Expected behavior

force=true should re-index the codebase with the same file-selection logic as the default re-index path. The output (file count, chunk count, indexed content) should be consistent across runs and equivalent to clear_index + index_codebase without force=true.

Actual behavior

force=true produces random garbage results:

  • File count varies wildly between runs (600, 200, 663, 400 on the same codebase)
  • Chunks always equal files (1 chunk per file), indicating the indexed content is trivial/empty (vs the correct 19 chunks/file ratio on default indexing)
  • The 1:1 chunk-per-file ratio strongly suggests force re-index is indexing junk directories that should be excluded (.venv/, .pytest_cache/, __pycache__/, .egg-info/ — files in these dirs are typically small/empty)
  • .gitignore exclusion logic does not appear to apply on the force re-index path

This makes the index unusable for semantic search — every force re-index produces different results, and search queries hit junk content instead of actual source code.

Root cause hypothesis

Looking at the source (per handlers.js in @zilliz/claude-context-mcp), force=true triggers a different code path that:

  1. Clears the Milvus collection during validation
  2. Calls indexCodebase() with false as the third arg (likely a clearBeforeIndex or similar flag)
  3. Does NOT use the same getEffectiveIgnorePatterns() / .gitignore loading path as the default re-index

The default re-index path correctly loads .gitignore patterns via getEffectiveIgnorePatterns(). The force re-index path appears to skip this loading, defaulting to broader file selection that pulls in .venv/, .pytest_cache/, and other ignored dirs.

Suggested fix

Either:

  1. Make force=true use the same getEffectiveIgnorePatterns() path as default re-index
  2. Or document that the "correct" way to force re-index is clear_index + index_codebase(force=false) — and either reject force=true outright or fix it to behave the same way

The current behavior is misleading — force=true returns success and a non-zero file count, but the indexed content is wrong.

Environment

  • @zilliz/claude-context-mcp v0.1.15 (latest as of 2026-07-28)
  • Vector DB: Milvus 2.x (self-hosted via Docker, localhost:19530)
  • Embedding provider: OpenAI-compatible API via OpenRouter (qwen/qwen3-embedding-8b, batch size 100)
  • Splitter: AST (splitter: 'ast')
  • Discovered while debugging code-context MCP indexing for the OpenClaw codebase at /workspace/projects/openclaw/

Related observations

I noticed two related but distinct issues while debugging this:

  1. get_indexing_status reports "completed" before the index has actually reached full coverage — on the OpenClaw codebase (29,456 git-tracked files), the default re-index path returned "fully indexed, 700 files" but continued to grow over time without any explicit re-index call. After ~10 minutes of waiting, the count had reached 3,000+ files and was still climbing. The MCP server appears to be doing background incremental indexing after marking the index "completed".

  2. The misleading "Codebase is already indexed. Use force=true to re-index." rejection message — when calling index_codebase without force=true on an already-indexed codebase, the tool rejects with this message, but the underlying behavior is that the background incremental indexer continues to scan and index new files. The rejection message is misleading and causes the calling agent (or human) to incorrectly conclude that incremental indexing is not happening.

Both of these are probably covered by #395 and #408 (or related) — but the force re-index garbage result bug I am reporting here is distinct from those.

Source: zilliztech/claude-context