| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
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.
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.
Lowest effort to confirm, highest cost if left alone — all silent:
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.
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 | Fixed | |
| 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 | Fixed |
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. |
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 main branch
Linux
| Back | FazBrowse Home | New Git URL |
Activity
docs: D-23 -- idlelib umbrella filed as python/cpython#154760
terryjreedy commented on Aug 13, 2026
Comments, questions, and requests.
69 findings: I presume this is 73 - 4 excluded 'duplicates'. Even with duplicates un-numbered, I would like to have seen the where-what-etc reports if prepared before exclusion or easily prepared after. The low number of duplicates suggest that most of the open reported bugs are design bugs rather than code bugs. I believe the latter get fixed faster.
Status: "Every row starts at Under analysis, .... A maintainer moving a row off Under analysis ..." confused me a bit since I initially read this as saying that rows were initially located under an Under analysis header and should be literally moved elsewhere. Clearer to me: "Every row starts with Status set to *Under Analysis, ... A maintainer clearing the Status field, leaving a single space, ...".
I am currently the main maintainer of IDLE. If I open a PR, either I should add the PR # or let you have it replace the default. But see Subissues below.
1626 + 281 + 573 = 2480. 2503 unique items says that 23 of 86 idle.rst items were not in the 3 other categories, but what doc items are there other that labeled/unlabed issues and PRs?
*8 Groupings and subissues: I don't want 1-finding issues attached here. I intend to open a subissue for each grouping, but I would like the following revisions in the groupings and corresponding reports. I could edit the post here but AFAIK, not the linked reports.
EDIT: Some findings apply to multiple files, possible unrelated. Hence edits below.
Move 69 parenmatch from Configuration to Completions.
EDIT: changed Editor to Completions because finding applies also to completions.
Move 67 runscript from Editor to Shell.
Move 7 of 16 non-pyshell findings in Shell to a new Run group.
Move 5 of 14 searchengine/replace findings in Editor to a new SearchReplace group.
EDIT: 47 in both replace and format, but want to split anyway. EDITOR has another format issue.
Devdanzin, how would you feel about moving the group lists to subissues? Copies might get out-of-sync. Or can group headers be made linkable? They would be fine, like the report section links, but I am not familiar with markdown and in particular, not with within-site github links.
Any questions for me?
terryjreedy commented on Aug 13, 2026
Continuing.
"unlabelled issues mentioning idlelib": Is this as broad as one occurrence of 'idlelib' (or 'IDLE') anywhere in an issue? I don't want multiple project issues fixing typos or otherwise updating code in multiple modules outside of idlelib. Some of those might only include idlelib in the diff itself. Just today a likely spam issue indirectly by linking to this issue (above my previous comment), though perhaps by accident. If I do check these, would there be any value in keeping an Exclude list?
Can just new code in 1 or more PRs be checked? Serhiy has proposed adding 4 new browsers in 4 issues, with code help from Claude. There is lots of new code.
On first reading, some of the findings are not clear to me as to the exact bug or the exact remedy suggested. At some point, would it be possible to check whether a subset of items are fixed without rerunning the entire analysis?
terryjreedy commented on Aug 23, 2026
@devdanzin In case you missed this, I have several questions.