Summary
pyscn analyze (and the underlying complexity analyzer) emits a function record named __main__ with start_line=0 and end_line=0 for Python files containing module-level executable code (no def main(): and no if __name__ == "__main__": block). Line range 0-0 is structurally impossible — every actual line of source is 1-indexed — so any consumer that opens the file at the reported range gets nothing.
This is mostly hidden because the synthetic __main__ typically lands in the low-risk bucket, but it shows up in the per-function list of every audit that includes scripts with module-level code, and in the juftin/browsr audit it surfaced at high enough complexity to land in the medium-risk list (where the impossible line range would actively mislead a user trying to navigate to it).
Repro (minimal synthetic)
# /tmp/scriptlike.py
import sys
count = 0
for arg in sys.argv[1:]:
if arg.startswith("--"):
count += 1
else:
count -= 1
print(count)
pyscn analyze --json --no-open /tmp/scriptlike.py
Expected behavior
Either:
- Don't emit a
__main__ record at all for module-level code (the file's "function" is the module itself; alternative: emit it with the actual source span, e.g. start_line=4, end_line=10 covering the executable statements), or
- Emit it with a structurally valid range matching the spanned source lines.
Actual output (excerpt from rainbow audit)
{
"Name": "__main__",
"FilePath": ".pyscn/audit/repos/ledger-donjon-rainbow/examples/HW_analysis/pin_fault.py",
"StartLine": 0,
"StartColumn": 0,
"EndLine": 0,
"Metrics": {
"Complexity": 5,
"CognitiveComplexity": 0,
"Nodes": ...
},
"RiskLevel": "low"
}
The actual file has 99 lines of source (functions setup_emulator at line 19, result at line 33, plus module-level loop at lines 38-99). pyscn correctly emits records for the two real functions; the __main__ entry is the synthesized one and is the only function with a 0-0 range.
Why this is a bug (not just imprecise)
The line range 0-0 is structurally impossible — a start_line == 0 && end_line == 0 record for a non-empty file violates the implicit contract of every other record pyscn emits (all of which use 1-indexed real lines). Tools consuming the JSON (IDEs, code-frame extractors, git blame integrations) will fail or produce empty output when they try to open the cited range. This makes the synthetic record indistinguishable from a real-but-malformed one.
Cross-repo evidence
This pattern was previously observed in:
juftin/browsr (audited 2026-04-25) — docs/gen_ref_pages.py:0-0, surfaced in the complexity findings list at medium risk
Ledger-Donjon/rainbow (audited 2026-04-27, this issue) — examples/HW_analysis/pin_fault.py:0-0, low risk
In both cases the __main__ record corresponds to module-level code, not a real def __main__ (which doesn't exist as a Python convention anyway — module entry points use if __name__ == "__main__": blocks, not a function literally named __main__).
Suggested fixes
- Skip the synthesis: the simplest fix is to not emit a
__main__ record at all when the only "function" is module-level code. Cyclomatic complexity at module scope is a known concept but isn't usually called __main__.
- Emit a real range: if the metric is genuinely useful (it can be), give it a span that covers the non-function statements (e.g., the smallest enclosing range of all top-level statements outside
def/class). That keeps the metric while making the record well-formed.
- Rename: if there's some internal reason to keep this record, rename it to something that makes its synthetic origin obvious (
<module>, __module__, <top-level>) so consumers can easily filter it.
pyscn version
v1.16.0-14-gf38cdbc
Found via the /pyscn-fp-audit skill in repo Ledger-Donjon/rainbow@f72101a (and previously in juftin/browsr).
Summary
pyscn analyze(and the underlying complexity analyzer) emits a function record named__main__withstart_line=0andend_line=0for Python files containing module-level executable code (nodef main():and noif __name__ == "__main__":block). Line range0-0is structurally impossible — every actual line of source is 1-indexed — so any consumer that opens the file at the reported range gets nothing.This is mostly hidden because the synthetic
__main__typically lands in the low-risk bucket, but it shows up in the per-function list of every audit that includes scripts with module-level code, and in thejuftin/browsraudit it surfaced at high enough complexity to land in the medium-risk list (where the impossible line range would actively mislead a user trying to navigate to it).Repro (minimal synthetic)
Expected behavior
Either:
__main__record at all for module-level code (the file's "function" is the module itself; alternative: emit it with the actual source span, e.g.start_line=4, end_line=10covering the executable statements), orActual output (excerpt from rainbow audit)
{ "Name": "__main__", "FilePath": ".pyscn/audit/repos/ledger-donjon-rainbow/examples/HW_analysis/pin_fault.py", "StartLine": 0, "StartColumn": 0, "EndLine": 0, "Metrics": { "Complexity": 5, "CognitiveComplexity": 0, "Nodes": ... }, "RiskLevel": "low" }The actual file has 99 lines of source (functions
setup_emulatorat line 19,resultat line 33, plus module-level loop at lines 38-99). pyscn correctly emits records for the two real functions; the__main__entry is the synthesized one and is the only function with a 0-0 range.Why this is a bug (not just imprecise)
The line range
0-0is structurally impossible — astart_line == 0 && end_line == 0record for a non-empty file violates the implicit contract of every other record pyscn emits (all of which use 1-indexed real lines). Tools consuming the JSON (IDEs, code-frame extractors,git blameintegrations) will fail or produce empty output when they try to open the cited range. This makes the synthetic record indistinguishable from a real-but-malformed one.Cross-repo evidence
This pattern was previously observed in:
juftin/browsr(audited 2026-04-25) —docs/gen_ref_pages.py:0-0, surfaced in the complexity findings list at medium riskLedger-Donjon/rainbow(audited 2026-04-27, this issue) —examples/HW_analysis/pin_fault.py:0-0, low riskIn both cases the
__main__record corresponds to module-level code, not a realdef __main__(which doesn't exist as a Python convention anyway — module entry points useif __name__ == "__main__":blocks, not a function literally named__main__).Suggested fixes
__main__record at all when the only "function" is module-level code. Cyclomatic complexity at module scope is a known concept but isn't usually called__main__.def/class). That keeps the metric while making the record well-formed.<module>,__module__,<top-level>) so consumers can easily filter it.pyscn version
v1.16.0-14-gf38cdbcFound via the
/pyscn-fp-auditskill in repoLedger-Donjon/rainbow@f72101a(and previously injuftin/browsr).