Deferred from PR #2542 review (Greptile finding on src/extractors/python.ts:296 / mirrored crates/codegraph-core/src/extractors/python.rs, find_enclosing_call_name / STDLIB_PROCESS_LAUNCHER_MODULES).
Background: #2420 needed to distinguish a wrapper call (sys.exit(main())) from the real entrypoint target (main) so only one gets role: 'entry'. Three review rounds converged on: a wrapper is excluded from bare-name resolution only if its import source is one of a small curated set of stdlib modules (sys, os, asyncio) — everything else (a local instance, a same-repo module import, a third-party package) flows through the normal bare-name resolution check.
Finding: the curated-set check matches by import source name only (import asyncio → source "asyncio"), not by whether that import actually resolved to a stdlib module vs. an in-repo file. If a repository defines its own top-level module literally named asyncio.py (or sys.py/os.py) and a qualifying guard calls asyncio.run(main()) intending to invoke that local module's run, the check still treats it as the external stdlib passthrough — main gets the label instead of run, reproducing the original #2420 bug for this shape.
Why this wasn't fixed inline: closing this gap precisely requires knowing whether the import actually resolved to an in-repo file, not just its source name. That information exists in the graph (as imports-kind edges, created during import resolution) but only after the build's import-resolution phase completes — extraction time (where the current wrapper/entrypointWrappedBy decision is made) runs before that. Deciding wrapper eligibility from real resolved-import data would mean moving (or duplicating) this decision into the post-hoc projection stage (projectEntrypointAttribution / project_entrypoint_attribution), correlating each wrapper's import statement to its actual resolved target — a real design change, not a quick patch, and squarely the kind of "needs its own design" work #2420's own original issue text flagged as out of scope for a first pass.
Severity/likelihood: low. Triggering this requires a repository to define its own module with the exact same name as a Python stdlib module (sys, os, or asyncio) — already unusual and arguably confusing/discouraged Python practice independent of this tool. Unlike the three shapes fixed in #2420 (a local instance, an unwrapped call, a same-repo module import under an ordinary name), this one needs a deliberate stdlib-name collision to manifest.
Suggested approach for whoever picks this up: extend the wrapper-eligibility check in projectEntrypointAttribution/project_entrypoint_attribution to consult the file's actual imports edges (or an equivalent resolved-import record) rather than (or in addition to) the curated stdlib-name set — i.e., only treat a wrapper as "definitely external" when its import source name is in the curated set and no imports edge exists from the guard's file to an in-repo target for that same source. Needs the equivalent change in both engines per the dual-engine architecture.
Deferred from PR #2542 review (Greptile finding on
src/extractors/python.ts:296/ mirroredcrates/codegraph-core/src/extractors/python.rs,find_enclosing_call_name/STDLIB_PROCESS_LAUNCHER_MODULES).Background: #2420 needed to distinguish a wrapper call (
sys.exit(main())) from the real entrypoint target (main) so only one getsrole: 'entry'. Three review rounds converged on: a wrapper is excluded from bare-name resolution only if its import source is one of a small curated set of stdlib modules (sys,os,asyncio) — everything else (a local instance, a same-repo module import, a third-party package) flows through the normal bare-name resolution check.Finding: the curated-set check matches by import source name only (
import asyncio→ source"asyncio"), not by whether that import actually resolved to a stdlib module vs. an in-repo file. If a repository defines its own top-level module literally namedasyncio.py(orsys.py/os.py) and a qualifying guard callsasyncio.run(main())intending to invoke that local module'srun, the check still treats it as the external stdlib passthrough —maingets the label instead ofrun, reproducing the original #2420 bug for this shape.Why this wasn't fixed inline: closing this gap precisely requires knowing whether the import actually resolved to an in-repo file, not just its source name. That information exists in the graph (as
imports-kind edges, created during import resolution) but only after the build's import-resolution phase completes — extraction time (where the current wrapper/entrypointWrappedBydecision is made) runs before that. Deciding wrapper eligibility from real resolved-import data would mean moving (or duplicating) this decision into the post-hoc projection stage (projectEntrypointAttribution/project_entrypoint_attribution), correlating each wrapper's import statement to its actual resolved target — a real design change, not a quick patch, and squarely the kind of "needs its own design" work #2420's own original issue text flagged as out of scope for a first pass.Severity/likelihood: low. Triggering this requires a repository to define its own module with the exact same name as a Python stdlib module (
sys,os, orasyncio) — already unusual and arguably confusing/discouraged Python practice independent of this tool. Unlike the three shapes fixed in #2420 (a local instance, an unwrapped call, a same-repo module import under an ordinary name), this one needs a deliberate stdlib-name collision to manifest.Suggested approach for whoever picks this up: extend the wrapper-eligibility check in
projectEntrypointAttribution/project_entrypoint_attributionto consult the file's actualimportsedges (or an equivalent resolved-import record) rather than (or in addition to) the curated stdlib-name set — i.e., only treat a wrapper as "definitely external" when its import source name is in the curated set and noimportsedge exists from the guard's file to an in-repo target for that same source. Needs the equivalent change in both engines per the dual-engine architecture.