Skip to content

indexer: extractor version bump never restages a quiet non-Merkle repository (census_noop bypasses the staleness check) #695

Description

@pbednarcik

While validating a rebuilt daemon over an existing store I hit a shape where an extractor version bump never lands: a repository with zero file churn takes the census_noop route on daemon warmup, and that route never consults the persisted extractor versions.

What I observed (Windows, current main at 116ff37 merged into my PR #677 branch; the mechanism is main's, my branch only raised the version number):

  • stored snapshot in repo_index_state: "csharp":15, no _post_extraction_policy key (written by the previous binary), so both staleness triggers were pending against the new binary (csharp at 16 plus policy epoch 2)
  • daemon restart over the unchanged tree:
daemon: reconciled repo from snapshot  route=census_noop  changed=0  deleted=0  stale_files_reindexed=0
  • the snapshot stayed at "csharp":15 after warmup, and repeated restarts reproduce it - the graph keeps serving the old extraction until some source file happens to change

Where it forks (internal/indexer/multi.go:2995):

case churn == 0 && !idx.merkleEnabled():
    route = "census_noop"
    result, err = idx.cleanCensusResult(ctx, detected, start)
case churn == 0:
    // The mtime census cannot prove a Merkle-enabled repository clean:
    // a missing baseline or extractor-salt change still requires the
    // content-addressed full-tree incremental check.
    route = "incremental"

The Merkle-enabled arm names extractor-salt drift as exactly the reason a clean census cannot be trusted, and routes to the full-tree incremental pass. The non-Merkle arm has no analogous guard: extractorVersionStaleLangSet is consulted only inside incrementalReindexPathsMode (indexer.go:5859), which census_noop never reaches. The R6 machinery from PR #432 and the restage tests in extractor_version_restage_test.go all pin the full-tree pass itself, which works; the reconcile routing just never runs that pass for a quiet repository unless Merkle is on.

Why I think this matters right now: the Julia extractor rollout is this exact shape. A store indexed by the previous release has .jl files with unchanged mtimes and a snapshot without a julia key. TestIncrementalReindex_NonMerkleNewlyTrackedLanguageRestages proves the full-tree pass restages it, but a default (non-Merkle) daemon upgrade with no pending edits takes census_noop and keeps the regex-era Julia graph across restarts indefinitely. Same for the EF Core csharp bump on any dormant C# repository, and for the _post_extraction_policy epoch itself.

Suggested fix, if you agree with the diagnosis: consult the staleness set during routing - when the stored snapshot reports version drift, treat churn==0 the same way the Merkle arm does and take the full-tree incremental route. The check is one stored-row read plus a map compare, and the restage re-stamps the snapshot so it fires once per bump. Happy to file a PR with a regression test at the reconcile-routing level if you want it.

I healed my own store with an untrack and retrack in the meantime, so nothing is blocked on my side.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions