Skip to content

Fixes #73: invalidate the incremental cache on extractor change, not just file change - #71

Open
learncoder4848 wants to merge 1 commit into
intuit:mainfrom
learncoder4848:fix/extractor-aware-content-hash
Open

Fixes #73: invalidate the incremental cache on extractor change, not just file change#71
learncoder4848 wants to merge 1 commit into
intuit:mainfrom
learncoder4848:fix/extractor-aware-content-hash

Conversation

@learncoder4848

@learncoder4848 learncoder4848 commented Aug 30, 2026

Copy link
Copy Markdown

Summary

Fixes #73 .

Module.content_hash only covers the file's bytes, so index() skips any file whose contents haven't changed — even when the extractor that parsed it has. Extractor fixes therefore never reach an existing index. 3.2.16's Java method_reference and C++ namespace-scoping fixes both silently missed anyone who didn't happen to run index --full.

LanguagePack now carries a fingerprint built from its entities.scm / relations.scm sources, the grammar's identity, its custom edge definitions, and a new EXTRACTOR_SCHEMA_VERSION for Rust-side changes. content_fingerprint folds that into the per-file hash.

It's scoped per pack, so a Java query fix re-extracts Java files and leaves Python and TypeScript cached. Bumping a single global version would also work, but would mean a full reindex on every release.

Two places computed the file hash independently — the skip gate in index_via_backend and extract_file. Both now go through content_fingerprint. If those ever drift apart nothing gets skipped and every index quietly becomes a full one, so there's a test pinning it.

Grammar plugins get the same treatment via plugin_fingerprint, over the plugin's plugin.toml, its .g4 grammars and its extractor source.

No schema migration — content_hash is already an opaque string in every backend.

One caveat: tree-sitter exposes no grammar version, so grammar identity is inferred from abi_version plus the node-kind / field / parse-state counts. An upgrade leaving all four unchanged would be missed. Rust-side extractor changes still need a manual EXTRACTOR_SCHEMA_VERSION bump. Both are documented in docs/CODE-PARSING.md.

Test plan

  • cargo test --all — 1263 passed, 0 failed (13 new)
  • cargo fmt --all -- --check and cargo clippy --all-targets -- -D warnings — clean
  • cargo check --features remote -p infigraph-core -p infigraph-cli -p infigraph-mcp — clean (CI builds default features only; the release workflow uses --features remote)
  • cargo test -p infigraph-core --test index_perf -- --ignoredtest_incremental_index_skips_unchanged still skips everything, confirming the skip gate and the stored hash agree

The main new one is test_extractor_change_reindexes_unedited_files: index a project, reopen with the same pack (nothing re-indexed), reopen with a changed pack (all re-indexed), reopen again with the changed pack (nothing). That last step is the one that matters — it proves this doesn't turn into a permanent reindex loop.

Notes

Adding a private field to LanguagePack is technically breaking for struct-literal construction downstream, so this probably wants a minor bump rather than a patch.

…hash

Module.content_hash was a SHA-256 of the file's bytes alone, so nothing
in the stored fingerprint reflected how the file was parsed -- no
grammar identity, no digest of the language pack's query files, no
extractor schema stamp. After a release that fixes an extractor (3.2.16
shipped both the Java method_reference capture and the C++
namespace-scoping fix), every existing index kept the old, wrong graph:
unedited files still matched their stored hash, so they were skipped
indefinitely unless the user knew to run index --full.

LanguagePack now carries a fingerprint built from its entities.scm /
relations.scm sources, grammar identity, custom edge definitions and a
new EXTRACTOR_SCHEMA_VERSION; content_fingerprint folds that into the
per-file hash. Invalidation is scoped per pack, so a Java query fix
re-extracts Java files and leaves other languages cached.

The skip gate in index_via_backend and extract_file each computed a file
hash independently -- if the two ever disagree, nothing is skipped and
every index silently becomes a full reindex -- so both now route through
content_fingerprint. Grammar plugins are covered by plugin_fingerprint
over plugin.toml, both ANTLR grammars and the extractor source.

No schema migration: content_hash is already an opaque string in every
backend.

13 new tests, including an end-to-end check that an upgraded extractor
re-extracts unedited files and then settles rather than reindexing
forever. Full workspace suite (1263 tests), fmt, clippy and
--features remote all pass.

Co-authored-by: Sarvesh Sawant <sarveshdev92@gmail.com>
@learncoder4848

Copy link
Copy Markdown
Author

@murari316 @johnintuit @sandeep-mewara @WinterQuant Can you guys review the changes ?

@learncoder4848 learncoder4848 changed the title fix(index): invalidate the incremental cache on extractor change, not just file change Fixes #73: invalidate the incremental cache on extractor change, not just file change Sep 1, 2026
@learncoder4848

learncoder4848 commented Sep 3, 2026

Copy link
Copy Markdown
Author

@murari316 @johnintuit @sandeep-mewara @WinterQuant @pradeepmouli

Reminder
Can you guys review the changes and start the workflow ?

@learncoder4848

Copy link
Copy Markdown
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Extractor fixes never reach existing indexes — content_hash only covers file bytes

1 participant