Skip to content

AST cache ignores the tree-sitter grammar version: stale extractions survive a grammar upgrade #4236

Description

@ULTARI-iOS

Summary

The AST cache key does not include the tree-sitter grammar version. After upgrading a grammar package (for example tree-sitter-swift 0.7.2 → 0.7.4), extract() keeps returning the results produced by the old grammar for every file whose content and path are unchanged. That includes results the new grammar would have fixed.

cache_dir() namespaces AST entries by graphify's own version and cache schema (ast/v{version}-s{schema}). file_hash() covers the file content and its relative path. Neither of these changes when only the grammar package changes.

Reproduction (graphifyy 0.9.80)

src/Badge.swift:

final class Badge {
    #if os(iOS)
    func iosOnly() {}
    #endif
    func update() {}
}

repro.py:

import sys
from importlib.metadata import version
from pathlib import Path
from graphify.extract import extract

root, cache = Path(sys.argv[1]), Path(sys.argv[2])
ast = extract([root / "Badge.swift"], cache_root=cache, root=root)
owned = sorted(e["target"] for e in ast["edges"] if e["relation"] == "method")
print(f"tree-sitter-swift {version('tree-sitter-swift')}: methods owned by Badge = {owned}")

Run it three times:

# 1) fresh cache, tree-sitter-swift 0.7.2 (cannot parse #if inside a type body)
tree-sitter-swift 0.7.2: methods owned by Badge = []
# 2) same cache, after upgrading to tree-sitter-swift 0.7.4
tree-sitter-swift 0.7.4: methods owned by Badge = []
# 3) empty cache, tree-sitter-swift 0.7.4
tree-sitter-swift 0.7.4: methods owned by Badge = ['badge_badge_iosonly', 'badge_badge_update']

Run 2 serves the 0.7.2 extraction from the cache, so the upgrade has no effect until the cache is deleted or the source file changes. Nothing reports that the cached entry came from a different grammar.

Impact

We found this while comparing two graphs built from the same Swift sources. One environment had upgraded tree-sitter-swift, but its warm cache still held the 0.7.2 results. As a result, roughly 400 members were detached from their types and their call edges were missing, even though the installed grammar parsed the files correctly.

Suggested fix

Include the version of the grammar used for each language in the AST cache namespace or key, for example the installed tree-sitter-<lang> distribution version and the tree-sitter runtime version. A content-only key is not enough, because the extractor's output depends on the grammar.

Workaround

Key cache_root by the grammar versions on the caller side, for example <cache>/ts{tree_sitter}-swift{tree_sitter_swift}.

Environment: graphifyy 0.9.80, tree-sitter 0.25.2, tree-sitter-swift 0.7.2 / 0.7.4, Python 3.11, macOS.

Activity

  1. github-actions commented on Oct 8, 2026

    @github-actions

    Thanks for opening this issue, @ULTARI-iOS. A maintainer will take a look soon.

    If you would like to discuss it in real time, come say hi on our Discord server. For longer-form questions and ideas there is also GitHub Discussions.

    To help us triage, please make sure the report includes what you expected, what actually happened, and the steps (and a small sample) to reproduce it.

  2. nothariharan commented on Oct 8, 2026

    @nothariharan
    Contributor

    Fixed in PR #4239.

    What changed:

    • graphify/cache.py: the AST cache directory is now cache/ast/v{graphifyy_version}-s{schema}-g{grammar_fingerprint}/.
    • _grammar_fingerprint() hashes the installed tree-sitter runtime plus every tree-sitter-* distribution's exact version (incl. tree-sitter-language-pack), memoized once per process.
    • Upgrading any grammar package therefore moves extract() to a new cache dir; the existing stale-dir sweep removes the old one, so stale post-upgrade results are never served and no manual cache clear is needed.

    Verified with new regression tests in tests/test_grammar_cache_key.py (fingerprint changes on grammar bump, stable for unchanged set, cache dir changes end-to-end). ruff check clean; reviewed by an agent for bypasses/test gaps (fixed: per-dist error isolation, non-vacuous stability test, end-to-end dir-change test).

  3. safishamsi commented on Oct 9, 2026

    @safishamsi
    Member

    Fixed by #4239 (thanks @nothariharan), shipped in v0.9.83. Closing as resolved.

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

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