Skip to content

Incremental update drops indirect_call edges to unchanged callable targets #2438

Closed

Description

@aryanbonigala

Summary

A full build resolves an imported callback passed to a recognized dispatcher into an indirect_call edge. Rebuilding only the caller drops that edge when the callable target's file is unchanged.

Reproduction

b.py:

def handler():
    return 1

a.py:

from b import handler


def run(pool):
    return pool.submit(handler)
  1. Full build: the run -> handler indirect_call edge is present.
  2. Edit only a.py (keeping the callback reference) and rebuild incrementally with changed_paths=[a.py].
  3. The indirect_call edge is gone. It only comes back after a full rebuild.

Why the #2406 fix does not cover this

The shared direct-call resolution context added in #2406 supplies persisted node identity (id, label, source_file, type) to the shared direct call pass. indirect_call needs something that context cannot carry:

So the guard correctly refuses to bind, and the edge is dropped rather than mis-resolved.

Possible directions

  • Persist the callable markers (or a narrower, explicitly-versioned "is invocable" flag) in a way that is safe to trust across rebuilds.
  • Or give the indirect-call resolver a separate conservative context that carries provenance strong enough to distinguish a callable definition from a same-named data symbol.

Either way the callability signal must be derived at extraction time and carried forward, never re-inferred from labels.

Constraints any fix must keep

Tracking

tests/test_watch.py::test_incremental_rebuild_drops_indirect_call_to_unchanged_target is an xfail(strict=True) covering exactly this reproduction. Closing this issue should make it pass and the marker should then be deleted.

Related to #2406 (which fixes the shared direct-call case only, and does not address this one).

Activity

  1. aryanbonigala commented on Aug 4, 2026

    @aryanbonigala
    ContributorAuthor

    I’m investigating this on top of the incremental resolution context from #2439, focusing on preserving callable provenance without exposing private extractor markers or weakening the existing false-positive guards.

  2. safishamsi commented on Aug 5, 2026

    @safishamsi
    Member

    Fixed in v0.9.33. The _callable/_callable_class markers now persist to graph.json (like _origin), so indirect_call edges from a changed file into an unchanged callable target survive incremental rebuilds. Thanks.

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