Bug report
Bug description:
A two-pass review of Lib/idlelib turned up 69 findings — 37 FIX (a wrong result, silent data loss, or an unreachable feature) and 32 CONSIDER. Every one carries a file:line, a concrete failure scenario, and — where idlelib already handles the same situation correctly somewhere else — the guarded twin that shows it is a defect by the project's own standard and suggests the fix.
I'm filing them under one umbrella so they can be picked off individually rather than flooding the tracker.
Please don't start work on a row until a maintainer has cleared it. These are a reviewer's findings, not triaged bugs: some will be intentional behaviour, some not worth the churn, and some wrong. Every row starts at Under analysis, which means not yet cleared — do not work on it. A maintainer moving a row off Under analysis is what opens it up; a PR against an uncleared row may be wasted effort, and several of these touch code where the current behaviour is load-bearing.
To take a cleared one: open a normal issue or PR for it and drop a comment here with the link — I'll put it in the Status column. If any row is a duplicate, a non-bug, or intentional, say so and I'll annotate it.
Found with code-review-toolkit. Reports were drafted with AI assistance (Claude Code) and then re-verified by hand — see Disclosure at the end.
Scope and verification
- Everything below is against
main at 993a0c65a7b (2026-07-27). The review itself was carried out on the 3.14 branch at 6080c866096; every finding was then re-checked against main by comparing the enclosing function (46 byte-identical, 13 changed and read individually, 11 module-level checked by hand), and every line number here is main's. Line numbers drift; the cited function and mechanism will not.
- 18 findings were reproduced against a running interpreter (real Tk where the widget matters); the rest were confirmed by reading the code and its tests. Each gist entry says which. The reproductions were re-run on a release build of main at the commit above.
python -m test test_idle passes on this commit (305 run, 83 skipped headless). Every finding below is one the suite cannot see — and four of them (26, 71, 72, 73) are about why.
- Prior art was checked for all 73 findings against the whole tracker: 1626
topic-IDLE issues, 281 unlabelled issues mentioning idlelib, 573 PRs and 86 idle.rst items (2503 unique). Three were already reported and have been removed from this report — they are listed at the bottom so nobody re-derives them. Where an open issue covers the area without covering the defect, the table names it.
Three systemic roots
These are worth reading first: each explains several of the individual rows below, and fixing the root is a smaller change than fixing its instances one at a time.
- 31 — Five independent hand-written recognizers for Python syntax with no shared source of truth
idlelib answers “is this Python syntax?” in five hand-written places with no shared definition, and new syntax reaches some and not others. colorizer is consistently the one that gets updated — four of the rows below are drift between it and a sibling.
- 32 — The
-n execution backend is missing five cross-cutting protections the RPC backend has
use_subprocess selects between two interchangeable execution backends, and every cross-cutting protection lives inside the RPC one rather than in a shared driver. SystemExit is the sharpest cell and the two are opposites: the RPC backend contains it and returns to the prompt, while -n asks “Do you want to exit altogether?” with default="yes" and raises out of mainloop(), so unsaved editor buffers get no save prompt. Ctrl-C, Restart and the recursion headroom are missing from -n too.
- 34 — WidgetRedirector swallows every TclError, so text.index() can never raise on any IDLE widget
except TclError: return "" in WidgetRedirector.dispatch intercepts every subcommand of every percolated Text — which is every EditorWindow, PyShell and OutputWindow. On the widgets idlelib actually uses, text.index() therefore never raises and never returns None, callers cannot tell “no selection” from “the call failed”, and three incompatible guard idioms have grown in one file.
Start here
Lowest effort to confirm, highest cost if left alone — all silent:
- 35 — Format Paragraph silently deletes characters from every non-comment line of a selection (
format.py:59)
- 36 — A literal % in any setting strips every open window's keybindings and leaves them off (
config.py:48)
- 27 — Copy-with-prompts drops the last line whenever the selection ends at column 10, 20, 100... (
pyshell.py:1034)
- 60 — Saving a file whose coding cookie cannot encode its text writes a BOM and keeps the cookie (
iomenu.py:324-326)
- 51 — restore_file_breaks appends without clearing, so Save As carries the old file's breakpoints over (
pyshell.py:268)
- 59 — _study1 has no f-string state, so a PEP 701 multi-line f-string is read as three statements (
pyparse.py:245)
- 43 — Three keysyms shipped in every keyset cannot be spelled by the key-configuration dialog (
config_key.py:15)
- 34 — WidgetRedirector swallows every TclError, so text.index() can never raise on any IDLE widget (
redirector.py:116)
Findings
The # column is a row number in this issue, not an identifier. Please cite this issue plus the row (“#‹this issue› row 35”) rather than the bare number on its own — a bare number does not autolink and does not lead anyone back here.
Status: Under analysis = awaiting a maintainer's call, not cleared for work · #N = filed, in progress · #N FIXED = filed and fixed · won't fix / intentional = closed by a maintainer.
Prior art names an existing issue where one is relevant: from = this is residue of that merged change; see = that issue covers the area but not this defect, so a comment there may be better than a new issue.
Shell, the -n backend, and breakpoints (15)
pyshell.py, run.py, rpc.py — full reports
| # |
Sev |
Location |
Finding |
Prior art |
Status |
| 2 |
FIX |
pyshell.py:499-540 |
One subprocess-accept timeout wedges IDLE permanently |
|
Under analysis |
| 8 |
FIX |
run.py:635-639,695 |
Stack Viewer reads an attribute only runcode() ever creates |
|
Under analysis |
| 27 |
FIX |
pyshell.py:1034 |
Copy-with-prompts drops the last line whenever the selection ends at column 10, 20, 100... |
from #95511 |
Under analysis |
| 28 |
FIX |
pyshell.py:1016 |
get_prompt_text zips a computed line range against actual lines |
from #95191 |
Under analysis |
| 32 |
FIX |
pyshell.py:792-803 |
The -n execution backend is missing five cross-cutting protections the RPC backend has |
|
Under analysis |
| 33 |
FIX |
pyshell.py:680 |
Under idle -n, file is silently IDLE's own path instead of the script's |
from #77165 |
Under analysis |
| 51 |
FIX |
pyshell.py:268 |
restore_file_breaks appends without clearing, so Save As carries the old file's breakpoints over |
|
Under analysis |
| 52 |
FIX |
pyshell.py:244 |
Breakpoints are keyed by the raw filename while windows are keyed by the normalized one |
|
Under analysis |
| 53 |
FIX |
pyshell.py:259 |
breakpoints.lst uses = as its delimiter, which is legal in a path |
|
Under analysis |
| 12 |
CONSIDER |
run.py:190-197 |
The subprocess's last-resort handler can itself raise NameError |
|
Under analysis |
| 13 |
CONSIDER |
rpc.py:358-361 |
Every OSError on recv becomes 'the peer hung up' |
|
Under analysis |
| 14 |
CONSIDER |
pyshell.py:804-821 |
endexecuting() is skipped if showtraceback() fails |
|
Under analysis |
| 15 |
CONSIDER |
rpc.py:258-260 |
Remote internal errors are reported only at a permanently-disabled debug level |
|
Under analysis |
| 18 |
CONSIDER |
rpc.py:130-141 |
Reconnect resets some per-connection state but not the packet read buffer |
|
Under analysis |
| 21 |
CONSIDER |
pyshell.py:564-574 |
terminate_subprocess catches every OSError though its comment names one |
|
Under analysis |
Configuration and the Settings dialog (9)
config.py, configdialog.py, config_key.py — full reports
| # |
Sev |
Location |
Finding |
Prior art |
Status |
| 1 |
FIX |
config.py:136-140 |
A failed config save deletes the user's config file |
|
Under analysis |
| 36 |
FIX |
config.py:48 |
A literal % in any setting strips every open window's keybindings and leaves them off |
|
Under analysis |
| 43 |
FIX |
config_key.py:15 |
Three keysyms shipped in every keyset cannot be spelled by the key-configuration dialog |
|
Under analysis |
| 48 |
FIX |
config.py:244 |
An invalid value in the DEFAULT config is swallowed where the user-config clause warns |
from #60715 |
Under analysis |
| 49 |
FIX |
config.py:605 |
The keybinding collision test compares whole lists, so a partial overlap is undetected and silent |
|
Under analysis |
| 61 |
FIX |
config.py:756 |
Help sources are numbered on write and sorted as strings on read, so entry 10 sorts before entry 2 |
see #71652 |
Under analysis |
| 62 |
FIX |
config_key.py:227 |
An advanced key binding stored as a Tk event sequence is read back as a whitespace-separated list |
see #55646, #152747 |
Under analysis |
| 50 |
CONSIDER |
config.py:199 |
The whole IDLE profile location is a function of the launch directory when HOME is unusable |
see #60066, #52478 |
Under analysis |
| 69 |
CONSIDER |
parenmatch.py:53 |
Two extensions' in-code fallback defaults disagree with the values actually shipped |
|
Under analysis |
Editor: formatting, undo, search/replace, save and run (14)
format.py, undo.py, replace.py, searchengine.py, iomenu.py, runscript.py, zoomheight.py — full reports
| # |
Sev |
Location |
Finding |
Prior art |
Status |
| 3 |
FIX |
replace.py:226-229 |
Replace's NULL-guard tests the call receiver instead of the match |
|
Under analysis |
| 5 |
FIX |
searchengine.py:146-151 |
Forward search abandons the rest of a line after a zero-width match |
|
Under analysis |
| 9 |
FIX |
zoomheight.py:66-105 |
A failed window probe leaves the user's window stuck maximized |
from #81220 |
Under analysis |
| 35 |
FIX |
format.py:59 |
Format Paragraph silently deletes characters from every non-comment line of a selection |
see #143774, #143775 |
Under analysis |
| 45 |
FIX |
replace.py:161 |
undo_block_start/stop is unpaired on the exception path in four places |
|
Under analysis |
| 60 |
FIX |
iomenu.py:324-326 |
Saving a file whose coding cookie cannot encode its text writes a BOM and keeps the cookie |
|
Under analysis |
| 67 |
FIX |
runscript.py:189 |
Run Module ignores the return of io.save(), so a failed save runs the old code from disk |
|
Under analysis |
| 11 |
CONSIDER |
iomenu.py:360-375 |
A bad print command orphans the user's unsaved source in /tmp |
|
Under analysis |
| 19 |
CONSIDER |
replace.py:172-188 |
Replace All's final selection uses the pre-replacement span |
|
Under analysis |
| 20 |
CONSIDER |
searchengine.py:85-89 |
getprog catches only re.PatternError, so OverflowError escapes to every caller |
|
Under analysis |
| 24 |
CONSIDER |
iomenu.py:253,261 |
Recent-files list is updated outside the success guard |
|
Under analysis |
| 46 |
CONSIDER |
undo.py:104 |
undo_block_stop does not guard the sentinel its own twin guards |
|
Under analysis |
| 64 |
CONSIDER |
format.py:340 |
Untabify rewrites the whole line where its tabify mirror rewrites only the indent |
|
Under analysis |
| 65 |
CONSIDER |
undo.py:294 |
DeleteCommand.undo reinserts text without tags where the Insert mirror round-trips them |
|
Under analysis |
The five hand-written syntax recognizers (7)
pyparse.py, hyperparser.py, codecontext.py, colorizer.py — full reports
| # |
Sev |
Location |
Finding |
Prior art |
Status |
| 30 |
FIX |
hyperparser.py:298 |
HyperParser's string-prefix scanner never learned f or t |
from #139742, #139756 |
Under analysis |
| 47 |
FIX |
codecontext.py:22 |
codecontext's BLOCKOPENERS lacks match/case, so those blocks never appear in Code Context |
from #77745, #25851 |
Under analysis |
| 59 |
FIX |
pyparse.py:245 |
_study1 has no f-string state, so a PEP 701 multi-line f-string is read as three statements |
see #77061 |
Under analysis |
| 63 |
FIX |
hyperparser.py:137-140 |
Bracket matching checks the bracket type on the backward scan only |
see #124451 |
Under analysis |
| 17 |
CONSIDER |
pyparse.py:44-48 |
A comment with no space after # is invisible to HyperParser |
|
Under analysis |
| 31 |
CONSIDER |
hyperparser.py:298 |
Five independent hand-written recognizers for Python syntax with no shared source of truth |
|
Under analysis |
| 58 |
CONSIDER |
pyparse.py:21 |
_synchre matches def but cannot match async def, making indent analysis quadratic in async modules |
see #77061 |
Under analysis |
Completions and calltips (5)
autocomplete*.py, calltip.py — full reports
| # |
Sev |
Location |
Finding |
Prior art |
Status |
| 4 |
FIX |
autocomplete.py:117,134 |
Autocomplete's mode filter never applies to ATTRS because ATTRS == 0 |
|
Under analysis |
| 6 |
FIX |
calltip.py:189 |
get_argspec guards two of its three touches of a user object |
|
Under analysis |
| 10 |
FIX |
autocomplete_w.py:238 |
A failed is_active check permanently disables the completion window's Configure handler |
from #26404 |
Under analysis |
| 29 |
FIX |
autocomplete_w.py:363-368 |
and binds tighter than or, so the modifier guard covers one completion mode of two |
|
Under analysis |
| 68 |
FIX |
autocomplete_w.py:454-491 |
The completion popup teardown is unguarded where the calltip's identical teardown guards every step |
|
Under analysis |
Infrastructure: redirector, file list, debugger, trees (9)
redirector.py, filelist.py, debugger*.py, tree.py, stackviewer.py — full reports
| # |
Sev |
Location |
Finding |
Prior art |
Status |
| 34 |
FIX |
redirector.py:116 |
WidgetRedirector swallows every TclError, so text.index() can never raise on any IDLE widget |
|
Under analysis |
| 54 |
FIX |
filelist.py:90 |
After a Name Conflict the displaced window is marked None instead of re-pointed, so the NEXT collision is silent |
|
Under analysis |
| 55 |
FIX |
debugger_r.py:271 |
DictProxy.getitem returns a repr, and only one of its two consumers strips the extra quotes |
|
Under analysis |
| 16 |
CONSIDER |
filelist.py:105-108 |
A failed getcwd() silently yields a relative path used as a window identity key |
|
Under analysis |
| 22 |
CONSIDER |
debugger.py:159-162 |
Debugger.close swallows every failure of quit() |
|
Under analysis |
| 25 |
CONSIDER |
debugger.py:55-56 |
Idb.user_exception calls into the GUI unguarded where its twin is guarded |
|
Under analysis |
| 56 |
CONSIDER |
debugger_r.py:35 |
Four id()-keyed debugger tables are never cleared, so memory grows with every debugger stop |
|
Under analysis |
| 57 |
CONSIDER |
tree.py:234 |
Tree redraw re-binds every node's canvas item and never deletes the bindings |
|
Under analysis |
| 70 |
CONSIDER |
stackviewer.py:12 |
Stack Viewer module globals pin every frame of the last exception for the process lifetime |
|
Under analysis |
Documentation and the generated in-app help (6)
Doc/library/idle.rst, help.py, help.html, config-main.def — full reports
| # |
Sev |
Location |
Finding |
Prior art |
Status |
| 37 |
FIX |
Doc/library/idle.rst:731-732 |
The documented sys.argv[0] contract for idle -r is wrong, and idle -h contradicts it correctly |
|
Under analysis |
| 38 |
FIX |
Doc/library/idle.rst:752 |
The network diagnostic in the docs names a port IDLE has never listened on |
|
Under analysis |
| 39 |
CONSIDER |
help.py:251 |
The in-app help is generated correctly, so every documentation error is faithfully shipped |
|
Under analysis |
| 40 |
CONSIDER |
Doc/library/idle.rst |
Documentation still names UI that was renamed or split two releases ago |
|
Under analysis |
| 41 |
CONSIDER |
config-main.def:48 |
Four config options are fully supported and completely invisible, and one has no consumer |
|
Under analysis |
| 44 |
CONSIDER |
mainmenu.py:80 |
Three virtual events are declared but unreachable, one of them from a live menu |
|
Under analysis |
Test-suite integrity (4)
idle_test/ — full reports
| # |
Sev |
Location |
Finding |
Prior art |
Status |
| 26 |
CONSIDER |
idle_test/test_autocomplete.py:241 |
Five tests in the suite cannot fail |
|
Under analysis |
| 71 |
CONSIDER |
idle_test/mock_idle.py:48 |
The shared test stubs cannot express the conditions the guards under test exist for |
|
Under analysis |
| 72 |
CONSIDER |
idle_test/test_searchengine.py:279 |
A test class freezes text.index to a constant, leaving seven mutations of live code undetected |
|
Under analysis |
| 73 |
CONSIDER |
idle_test/test_undo.py |
Four test files exit 0 even when their tests fail |
|
Under analysis |
Excluded — please don't pursue these
Found by the review and then dropped — three because they are already reported, one because main already fixed it. Listed only so the same ground isn't covered twice:
| Finding |
Why it is not in the tables |
Custom run arguments are corrupted by a Tcl list round-trip (query.py:352,368-376) |
already reported: #93016 |
A bare except guards a four-link attribute chain (autocomplete.py:173-175) |
already reported: #59518 |
<<python-context-help>> is offered in Settings, bound by nothing, and has never had a handler (config.py:614) |
already reported: #88740 |
Configure IDLE → Keys raises KeyError for any third-party extension that ships disabled (config.py:486 on 3.14) |
fixed on main by gh-89520 / GH-28713 — GetExtensionKeys now uses current_keyset.get(event, None). Still present on the 3.14 branch; a backport may be wanted. |
Disclosure & caveats
- The reports were drafted with AI assistance (Claude Code); each gist carries an explicit disclaimer. Before publishing, every cited line was re-read in the tree at the stated commit, and every "Verified" transcript was produced by running the code.
- That re-verification changed three findings, which is the reason it is worth mentioning: 59 was upgraded from an argument to a reproduction by a differential its author had not run; a fourth claim (about how many test files return 0 on failure) was wrong by an order of magnitude and is corrected in 73; and two line numbers were off.
- 33 is deliberate in part. The commit that introduced the
-n exclusion says so in its own message. What is reported is only the part that commit did not consider: under -n the name is not absent, it is IDLE's own entry point.
- Severities are the reviewer's, not a maintainer's.
CONSIDER items in particular are judgement calls and several may be intentional; say so and I'll annotate them.
Each finding is also classified against a catalogue of recurring Python bug shapes; the shape name in each gist entry (zip-truncates-on-length-mismatch, per-line-property-derived-from-the-aggregate, …) is what let the review hunt siblings of a confirmed defect rather than stop at the first instance. Several rows here were found that way.
CPython versions tested on:
CPython main branch
Operating systems tested on:
Linux
Bug report
Bug description:
A two-pass review of
Lib/idlelibturned up 69 findings — 37FIX(a wrong result, silent data loss, or an unreachable feature) and 32CONSIDER. Every one carries afile:line, a concrete failure scenario, and — where idlelib already handles the same situation correctly somewhere else — the guarded twin that shows it is a defect by the project's own standard and suggests the fix.I'm filing them under one umbrella so they can be picked off individually rather than flooding the tracker.
Please don't start work on a row until a maintainer has cleared it. These are a reviewer's findings, not triaged bugs: some will be intentional behaviour, some not worth the churn, and some wrong. Every row starts at Under analysis, which means not yet cleared — do not work on it. A maintainer moving a row off Under analysis is what opens it up; a PR against an uncleared row may be wasted effort, and several of these touch code where the current behaviour is load-bearing.
To take a cleared one: open a normal issue or PR for it and drop a comment here with the link — I'll put it in the Status column. If any row is a duplicate, a non-bug, or intentional, say so and I'll annotate it.
Found with code-review-toolkit. Reports were drafted with AI assistance (Claude Code) and then re-verified by hand — see Disclosure at the end.
Scope and verification
mainat993a0c65a7b(2026-07-27). The review itself was carried out on the 3.14 branch at6080c866096; every finding was then re-checked against main by comparing the enclosing function (46 byte-identical, 13 changed and read individually, 11 module-level checked by hand), and every line number here is main's. Line numbers drift; the cited function and mechanism will not.python -m test test_idlepasses on this commit (305 run, 83 skipped headless). Every finding below is one the suite cannot see — and four of them (26, 71, 72, 73) are about why.topic-IDLEissues, 281 unlabelled issues mentioning idlelib, 573 PRs and 86idle.rstitems (2503 unique). Three were already reported and have been removed from this report — they are listed at the bottom so nobody re-derives them. Where an open issue covers the area without covering the defect, the table names it.Three systemic roots
These are worth reading first: each explains several of the individual rows below, and fixing the root is a smaller change than fixing its instances one at a time.
idlelib answers “is this Python syntax?” in five hand-written places with no shared definition, and new syntax reaches some and not others.
colorizeris consistently the one that gets updated — four of the rows below are drift between it and a sibling.-nexecution backend is missing five cross-cutting protections the RPC backend hasuse_subprocessselects between two interchangeable execution backends, and every cross-cutting protection lives inside the RPC one rather than in a shared driver.SystemExitis the sharpest cell and the two are opposites: the RPC backend contains it and returns to the prompt, while-nasks “Do you want to exit altogether?” withdefault="yes"and raises out ofmainloop(), so unsaved editor buffers get no save prompt. Ctrl-C, Restart and the recursion headroom are missing from-ntoo.except TclError: return ""inWidgetRedirector.dispatchintercepts every subcommand of every percolated Text — which is every EditorWindow, PyShell and OutputWindow. On the widgets idlelib actually uses,text.index()therefore never raises and never returnsNone, callers cannot tell “no selection” from “the call failed”, and three incompatible guard idioms have grown in one file.Start here
Lowest effort to confirm, highest cost if left alone — all silent:
format.py:59)config.py:48)pyshell.py:1034)iomenu.py:324-326)pyshell.py:268)pyparse.py:245)config_key.py:15)redirector.py:116)Findings
The
#column is a row number in this issue, not an identifier. Please cite this issue plus the row (“#‹this issue› row 35”) rather than the bare number on its own — a bare number does not autolink and does not lead anyone back here.Status: Under analysis = awaiting a maintainer's call, not cleared for work ·
#N= filed, in progress ·#N FIXED= filed and fixed · won't fix / intentional = closed by a maintainer.Prior art names an existing issue where one is relevant: from = this is residue of that merged change; see = that issue covers the area but not this defect, so a comment there may be better than a new issue.
Shell, the
-nbackend, and breakpoints (15)pyshell.py,run.py,rpc.py— full reportspyshell.py:499-540run.py:635-639,695pyshell.py:1034pyshell.py:1016pyshell.py:792-803-nexecution backend is missing five cross-cutting protections the RPC backend haspyshell.py:680idle -n, file is silently IDLE's own path instead of the script'spyshell.py:268pyshell.py:244pyshell.py:259=as its delimiter, which is legal in a pathrun.py:190-197rpc.py:358-361pyshell.py:804-821rpc.py:258-260rpc.py:130-141pyshell.py:564-574Configuration and the Settings dialog (9)
config.py,configdialog.py,config_key.py— full reportsconfig.py:136-140config.py:48config_key.py:15config.py:244config.py:605config.py:756config_key.py:227config.py:199parenmatch.py:53Editor: formatting, undo, search/replace, save and run (14)
format.py,undo.py,replace.py,searchengine.py,iomenu.py,runscript.py,zoomheight.py— full reportsreplace.py:226-229searchengine.py:146-151zoomheight.py:66-105format.py:59replace.py:161iomenu.py:324-326runscript.py:189iomenu.py:360-375replace.py:172-188searchengine.py:85-89iomenu.py:253,261undo.py:104format.py:340undo.py:294The five hand-written syntax recognizers (7)
pyparse.py,hyperparser.py,codecontext.py,colorizer.py— full reportshyperparser.py:298fortcodecontext.py:22pyparse.py:245hyperparser.py:137-140pyparse.py:44-48hyperparser.py:298pyparse.py:21defbut cannot matchasync def, making indent analysis quadratic in async modulesCompletions and calltips (5)
autocomplete*.py,calltip.py— full reportsautocomplete.py:117,134calltip.py:189autocomplete_w.py:238autocomplete_w.py:363-368andbinds tighter thanor, so the modifier guard covers one completion mode of twoautocomplete_w.py:454-491Infrastructure: redirector, file list, debugger, trees (9)
redirector.py,filelist.py,debugger*.py,tree.py,stackviewer.py— full reportsredirector.py:116filelist.py:90debugger_r.py:271filelist.py:105-108debugger.py:159-162debugger.py:55-56debugger_r.py:35tree.py:234stackviewer.py:12Documentation and the generated in-app help (6)
Doc/library/idle.rst,help.py,help.html,config-main.def— full reportsDoc/library/idle.rst:731-732idle -ris wrong, andidle -hcontradicts it correctlyDoc/library/idle.rst:752help.py:251Doc/library/idle.rstconfig-main.def:48mainmenu.py:80Test-suite integrity (4)
idle_test/— full reportsidle_test/test_autocomplete.py:241idle_test/mock_idle.py:48idle_test/test_searchengine.py:279idle_test/test_undo.pyExcluded — please don't pursue these
Found by the review and then dropped — three because they are already reported, one because main already fixed it. Listed only so the same ground isn't covered twice:
query.py:352,368-376)autocomplete.py:173-175)<<python-context-help>>is offered in Settings, bound by nothing, and has never had a handler (config.py:614)KeyErrorfor any third-party extension that ships disabled (config.py:486on 3.14)GetExtensionKeysnow usescurrent_keyset.get(event, None). Still present on the 3.14 branch; a backport may be wanted.Disclosure & caveats
-nexclusion says so in its own message. What is reported is only the part that commit did not consider: under-nthe name is not absent, it is IDLE's own entry point.CONSIDERitems in particular are judgement calls and several may be intentional; say so and I'll annotate them.Each finding is also classified against a catalogue of recurring Python bug shapes; the shape name in each gist entry (
zip-truncates-on-length-mismatch,per-line-property-derived-from-the-aggregate, …) is what let the review hunt siblings of a confirmed defect rather than stop at the first instance. Several rows here were found that way.CPython versions tested on:
CPython main branch
Operating systems tested on:
Linux