Skip to content

Snapshot 0/0 entries create infinite force-reindex loop #295

@EricSeastrand

Description

@EricSeastrand

Bug description

When a force reindex clears the Milvus collection, the snapshot file (~/.context/mcp-codebase-snapshot.json) is written with indexedFiles: 0, totalChunks: 0. If the subsequent indexing fails (network error, OOM, process killed, etc.), the snapshot is left at 0/0 permanently with status completed or indexfailed.

Every new Claude Code session sees the 0/0 entry, treats it as "not indexed", and the client passes force: true to index_codebase. This nukes the real Milvus collection (which may still have valid data) and writes 0/0 again — creating an infinite reindex loop.

Reproduction steps

  1. Index a codebase successfully (e.g. 13,000+ chunks)
  2. Kill the MCP server process mid-reindex (or simulate a network failure to the embeddings server)
  3. Observe ~/.context/mcp-codebase-snapshot.json — the entry will show indexedFiles: 0, totalChunks: 0
  4. Start a new Claude Code session in that project
  5. The session calls index_codebase with force: true because the snapshot says 0/0
  6. This nukes the Milvus collection and writes 0/0 again
  7. Repeat forever

Root cause

Three interacting issues:

1. Recovery path hardcodes 0/0

In handlers.ts, the search fallback recovery writes:

this.snapshotManager.setCodebaseIndexed(absolutePath,
  { indexedFiles: 0, totalChunks: 0, status: completed as const });

It never queries Milvus for actual row counts.

2. No startup validation against Milvus

loadCodebaseSnapshot() in snapshot.ts loads the snapshot from disk as-is. The v0.1.5 fix (PR #282) resets indexing entries to indexfailed, but does NOT validate completed entries with 0/0 stats against the actual Milvus backend. There is no check that says "this entry claims 0 files indexed — does the collection actually have data?"

3. Force reindex writes 0/0 before indexing starts

In handlers.ts:

this.snapshotManager.setCodebaseIndexing(absolutePath, 0);
this.snapshotManager.saveCodebaseSnapshot();

If indexing fails after this point, the snapshot has 0/0 and the v0.1.5 startup fix converts it to indexfailed — but the real collection data was already nuked by the force path.

Observed behavior

Current snapshot on my system shows 4 of 5 codebases at 0/0 despite having real Milvus collections with data:

indexed    files=    0 chunks=    0  /home/eric/bu-aws          (Milvus: 13,395 rows)
indexed    files=    0 chunks=    0  /home/eric/ln-marketing-brief (Milvus: 23 rows)
indexed    files=    0 chunks=    0  /home/eric/page-toolkit     (Milvus: 474 rows)
indexed    files=    0 chunks=    0  /home/eric/wow-addons       (Milvus: 87 rows)
indexed    files=  918 chunks= 2525  /bulk-storage/repos/mission-control (OK)

Manually patching the snapshot to reflect real Milvus row counts fixes it temporarily, but the bug re-triggers within hours.

Suggested fix

  1. Startup validation: In loadCodebaseSnapshot(), for any entry with status: completed and indexedFiles: 0, totalChunks: 0, query Milvus collection.stats() for actual row count. If the collection exists with data, update the snapshot. If no collection exists, remove the entry.

  2. Recovery path should query stats: Change the search fallback recovery from hardcoded { indexedFiles: 0, totalChunks: 0 } to querying actual Milvus collection stats before writing the snapshot entry.

  3. Never write completed with 0/0: setCodebaseIndexed should reject or warn when called with zero files — either get real stats or mark as needs-reindex.

Environment

  • @zilliz/claude-context-mcp v0.1.6 (latest as of 2026-04-01)
  • Milvus 2.x (self-hosted via Docker)
  • Self-hosted embeddings server
  • Claude Code CLI

Related issues

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions