Skip to content

aft_callgraph never resolves cross-file Python call edges; callers under-report and disclosure only covers zero-caller answers #1130

Closed as duplicate

Description

@tobwen

What happened?

aft_callgraph callers finds no cross-file call sites in Python code. A function called from another file through an ordinary static import — top-level from mod import func or a function-local from mod import func inside a function body — is reported as having 0 callers, and call_tree marks the call resolved: false. The same edge resolves normally in TypeScript.

Reproduction (files placed directly in a fresh project directory, engine configured on it):

mod.py:

def toplevel_target():
    return 1


def local_target():
    return 2


def test_target():
    return 3


def same_file_caller():
    return toplevel_target()

caller_toplevel.py:

from mod import toplevel_target


def run_toplevel():
    return toplevel_target()

caller_local.py:

def run_local():
    from mod import local_target
    return local_target()

ts_mod.ts:

export function tsTarget(): number {
  return 7;
}

ts_caller.ts:

import { tsTarget } from "./ts_mod";

export function runTsCaller(): number {
  return tsTarget();
}

Requests and observed results on a binary built from main at a5b9b8a2f (includes #1015), isolated storage, after the call-graph build finished:

  • {"command":"callers","file":"<root>/mod.py","symbol":"toplevel_target"} — returns only the same-file caller (same_file_caller:17); the cross-file caller in caller_toplevel.py is missing. The response carries no incompleteness marker.
  • {"command":"callers","file":"<root>/mod.py","symbol":"local_target"} — 0 callers, complete: false, unresolved_call_sites: 36, incomplete_reason: "Incomplete: 36 call sites in the analyzed Python code could not be resolved, so a dynamic or indirect call may still reach local_target."
  • {"command":"callers","file":"<root>/mod.py","symbol":"test_target","includeTests":true} — 0 callers with the same disclosure (37 unresolved sites).
  • {"command":"callers","file":"<root>/ts_mod.ts","symbol":"tsTarget"} — resolves the cross-file edge (runTsCaller:4).
  • {"command":"call_tree","file":"<root>/caller_toplevel.py","symbol":"run_toplevel","depth":1} — child toplevel_target with resolved: false.
  • {"command":"call_tree","file":"<root>/caller_local.py","symbol":"run_local","depth":1} — child local_target with resolved: false.

After adding the same-file caller to mod.py, callers toplevel_target returned exactly that one same-file caller, so same-file Python edges resolve while cross-file Python edges do not. The import forms involved are plain static from imports (no importlib, no exec, no dynamic module names).

Two observations on the current state:

  1. Cross-file Python call edges are never resolved, so callers/impact under-report for Python. With an unresolved call site demonstrably present, the answer for a symbol that also has a same-file caller carries no incompleteness marker at all (first request above), and zero-caller answers disclose only a generic count of unresolved call sites rather than pointing at cross-file imports.
  2. On the shipped 0.62.0 release the same probe returns a bare 0 callers with no disclosure.

Expected: callers finds the cross-file call sites ("Find all call sites of a symbol"), or at minimum discloses the gap whenever unresolved call sites to the queried symbol exist in the analyzed code — not only on zero-caller answers.

Diagnostics

npx @cortexkit/aft@latest doctor --issue was run in a non-interactive terminal and declined to collect a report ("Non-interactive terminal — not collecting or filing automatically"). Filled in manually below.

Plugin version

0.63.0 (packages/opencode-plugin at the checked-out main)

AFT binary version

Reproduced on both: 0.62.0 (shipped release, daemon binary ~/.cache/aft/bin/v0.62.0/aft) and a release build of main at a5b9b8a2f (includes #1015). Same results apart from the zero-caller disclosure, which only the main build emits.

Platform

linux x64

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