[3.14] gh-152682: Fix NULL dereference on OOM in `symtable_visit_type_param_bound_or_default` (GH-152684) (#152696)
In `symtable_visit_type_param_bound_or_default()`, when a reserved name
(e.g. `__classdict__`) is used as a type parameter, `PyUnicode_FromFormat()`
is called to build the SyntaxError message. If the allocation fails and
returns NULL, the subsequent `PyErr_SetObject()` and `Py_DECREF()` calls
would dereference NULL, causing a segfault.
Fix by returning 0 immediately when `PyUnicode_FromFormat()` returns NULL.
This propagates the MemoryError set by `PyUnicode_FromFormat()`.
[3.14] gh-153141: Fix mutable default argument in _SharedMemoryTracker.__init__ (GH-153142) (#153146)
gh-153141: Fix mutable default argument in _SharedMemoryTracker.__init__ (GH-153142)
Fix default argument for segment_names in _SharedMemoryTracker constructor to not use a mutable list.
(cherry picked from commit d733b104d53e96584a6881d2772df65ad82573a0)
[3.14] gh-79638: Restore "Treat an unreachable robots.txt as disallow all" (GH-152525) (GH-153108)
This change was accidentally reverted by f0daba1652c (gh-106693,
GH-149514), which only intended to revert the ob_sval change.
The tests were already restored by GH-149569.
(cherry picked from commit 70100b9ea0b43a95226eee45652a5735bb91e9c7)
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
[3.14] gh-69134: Wait until mapped in SimpleDialog keyboard tests (GH-152690) (GH-153105)
The SimpleDialog keyboard tests generate key events after focus_force(),
which on Windows are dropped until the toplevel is mapped, so they could
fail intermittently (seen on the Windows10 buildbot as
test_return_no_default). Wait until the window is mapped in these tests,
as GH-152599 did for the other keyboard tests.
[3.14] gh-76607: Improve documentation of the tkinter cursor option (GH-152481) (GH-153111)
Link to the cursors(3tk) manual page for the available cursor names and
note that they are platform-dependent.
(cherry picked from commit ea5703735f2ff4ba7bc3b0c7e746a44cb9b25081)
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Tk geometry strings can contain a negative offset (e.g. 200x100+-9+-8)
when a window edge is positioned beyond the corresponding screen edge.
Note this in the geometry() and winfo_geometry() documentation.
(cherry picked from commit 19b5e8e1f510eb2fdd3f6261006b61f8cf441101)
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
[3.14] gh-54930: Send a status line in error responses to malformed request lines (GH-152980) (GH-153093)
Previously such error responses were sent in the bare HTTP/0.9 style,
without a status line and headers.
(cherry picked from commit 2ab620b41af57e24c11c131bec02f682561ecfaa)
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
A Tk variable wrapper unsets its Tcl variable when garbage collected, so a
reference must be kept while a widget uses it. Otherwise Tk recreates the Tcl
variable but never unsets it again, leaking it.
(cherry picked from commit 820b6ca22d72482e2e68dd738cc370535c83655e)
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
[3.14] gh-151819: Clarify the conditional-pattern email example in re docs (GH-153072) (GH-153074)
The example pattern does not fail to match '<user@host.com' outright --
re.search finds 'user@host.com' in it; it only fails to match the
whole string. Reword to say so.
(cherry picked from commit fe9c7213e6576e27ebff6ef039706cd463593310)
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
[3.14] gh-40038: Quote imaplib command arguments when necessary (GH-152703) (GH-153069)
Argument quoting was inadvertently disabled when imaplib was ported to
Python 3 (bpo-1210 commented out the ``_checkquote()`` call, bpo-9638
then removed it), so since Python 3.0 commands failed for arguments
containing protocol-sensitive characters, such as a space in a mailbox
name.
Quoting is restored and reimplemented per the RFC 3501 grammar, so that
arguments that need quoting are escaped and quoted, while flags, sequence
sets and list wildcards are left intact.
For backward compatibility, an argument already enclosed in double quotes
is left unchanged, so code that quotes arguments itself keeps working.
(cherry picked from commit 600e86490fddf4732e8b993058ca011cc6387464)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
[3.14] gh-153030: Fix quadratic complexity in incremental parsing in HTMLParser (GH-153031) (GH-153039)
When an unterminated construct (e.g. a tag or comment) spanned many
feed() calls, rescanning the growing buffer and concatenating new data
onto it were both quadratic. New data is now accumulated in a list and
only joined and parsed once enough has piled up.
(cherry picked from commit bcf98ddbc40ec9b3ee87da0124a5660b19b7e606)
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Harjoth Khara <harjoth.khara@gmail.com> Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Co-authored-by: Stan Ulbrych <stan@python.org>
[3.14] gh-135661: Fix abrupt closing of empty comment in HTMLParser (GH-153007) (GH-153025)
An abruptly closed empty comment ("<!-->" or "<!--->") no longer extends
up to a later "-->" in the same feed() call.
test_htmlparser now also feeds each string source as a single chunk, in
addition to one character at a time, to exercise different input buffering.
(cherry picked from commit ed370d3337c1ac4f7295b444a4807c0b72287608)
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
[3.14] gh-83386: Enable test_hang_gh83386 for ProcessPoolExecutor (GH-152976) (#152983)
gh-83386: Enable test_hang_gh83386 for ProcessPoolExecutor (GH-152976)
The hang this test guards against (interpreter exit after
shutdown(wait=False) with running futures) was fixed for
ProcessPoolExecutor by the executor management rewrite years ago, but
the test still skipped it citing the issue. The skip also hid a latent
NameError in the subprocess template, which only selects a start method
in the process pool variants; rewrite it to use the same
mp_context=get_context() shape as the sibling templates. Remove a stale
comment claiming ProcessPoolExecutor often hangs with wait=False.
(cherry picked from commit 548c7314138b85cab3b945a1ce05440dd836a2ea)
Co-authored-by: Gregory P. Smith <68491+gpshead@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Terry Jan Reedy [Sat, 4 Jul 2026 03:24:34 +0000 (23:24 -0400)]
[3.14] gh-89520: Load extension settings and keybindings from user config (GH-28713) (#152992)
Extension keybindings defined in ~/.idlerc/config-extensions.cfg
were silently ignored because GetExtensionKeys, __GetRawExtensionKeys,
and GetExtensionBindings only checked default config.
Fix these to check user config as well, and update the extensions config
dialog to handle user-only extensions correctly.
---------
Co-authored-by: blurb-it[bot] <43283697+blurb-it[bot]@users.noreply.github.com> Co-authored-by: Jelle Zijlstra <jelle.zijlstra@gmail.com> Co-authored-by: Gregory P. Smith <68491+gpshead@users.noreply.github.com> Co-authored-by: Gregory P. Smith <greg@krypto.org>
(cherry picked from commit 208195dff4cd19dfd4aeb0eed8a133f2b1a66ec1)
[3.14] gh-150880: Normalize paths on Windows before appending wildcard (GH-152906) (GH-152964)
gh-150880: Normalize paths on Windows before appending wildcard (GH-152906)
This ensures that we don't turn a "valid" path with trailing spaces into an invalid path with embedded spaces.
(cherry picked from commit 1b4135a2c69f5320dbdcb37dcc295a5fd36580b5)
[3.14] gh-152785: Upgrade GCC from 10 to 13 in GHA Address Sanitizer (GH-152782) (#152948)
gh-152785: Upgrade GCC from 10 to 13 in GHA Address Sanitizer (GH-152782)
Remove the step which installs GCC 10. This step was needed when
Ubuntu only had GCC 9. We are now using Ubuntu 24.04 which has GCC
13.
(cherry picked from commit 31a0e0968e92c65019e4e0547475904e9b047d4a)
Co-authored-by: Victor Stinner <vstinner@python.org>
[3.14] gh-115634: Fix ProcessPoolExecutor deadlock with max_tasks_per_child (GH-140900) (#152927)
gh-115634: Fix ProcessPoolExecutor deadlock with max_tasks_per_child (GH-140900)
The idle worker semaphore counts task completions, not idle workers, so
it can hold a stale token released by a worker that later exited upon
reaching its max_tasks_per_child limit. The worker replacement path
consumed such tokens and skipped spawning a replacement, deadlocking
the remaining queued tasks once no workers were left.
Replace dead workers based on len(self._processes) without consulting
the semaphore. The submit() path is unchanged, preserving on-demand
spawning and idle worker reuse.
Replace the documentation note added in GH-140897 with a versionchanged
entry now that the bug is fixed.
[3.14] gh-152680: Detect virtualization on Windows in pythoninfo (GH-152824) (#152889)
gh-152680: Detect virtualization on Windows in pythoninfo (GH-152824)
Use WMI to detect virtualization on Windows.
Replace wmic command with _wmi module to get the operating system caption and
version.
The wmic tool is deprecated since January 2024:
https://techcommunity.microsoft.com/blog/windows-itpro-blog/wmi-command-line-wmic-utility-deprecation-next-steps/4039242
For example, it's no longer installed in Windows images on GitHub
Action.
Fix also run_command(): no longer try to spawn a subprocess if the
platform doesn't support subprocess. It avoids logging run_command()
errors.
(cherry picked from commit 4e4869b62fa91e26117b1d9136f60b58c9f505a5)
Co-authored-by: Victor Stinner <vstinner@python.org>
[3.14] gh-88574: Skip a spurious blank line after a literal in imaplib (GH-152751) (GH-152885)
Some IMAP servers send an extra blank line after the data of a literal.
imaplib mistook it for the response trailer and failed on the next
command. Such a blank line is now skipped.
(cherry picked from commit 53ff1a28ccfa69925c3c452cfc003970640fa65b)
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
[3.14] gh-66335: Add tests for imaplib command methods (GH-152872) (GH-152874)
Add coverage tests for the IMAP4 command methods and their UID variants
(SELECT, CREATE, COPY, STORE, FETCH, SEARCH, SORT, THREAD, DELETE,
RENAME, SUBSCRIBE, UNSUBSCRIBE, LIST, STATUS, the ACL and quota
commands, ANNOTATION, PROXYAUTH and ENABLE), plus test helpers
(splitargs, parse_sequence_set, make_simple_handler) and per-command
argument capture in the test server.
(cherry picked from commit a50b089b7c46e3068d9177b5f18ac767a31a18cd)
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
[3.14] gh-108280: Give a meaningful error for an invalid imaplib greeting (GH-152768) (GH-152857)
Connecting to a server that does not send a valid IMAP4 greeting, such as
a POP3 server answering on the IMAP port, failed with the unhelpful
"imaplib.IMAP4.error: None". A meaningful message is now raised instead.
(cherry picked from commit 262b6a0df5a325e788928ab0cd514fa4786571b4)
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
[3.14] gh-63121: Refresh imaplib capabilities on state changes (GH-152752) (GH-152855)
imaplib fetched the server capabilities only once, at connection time.
They are now also refreshed after a successful LOGIN or AUTHENTICATE,
from the CAPABILITY response the server sent or, if it sent none, by
querying it. This lets methods such as enable() see capabilities added
after login, for example ENABLE on Gmail (gh-103451).
Capabilities advertised in the server greeting are now used too, saving
a redundant CAPABILITY command.
(cherry picked from commit c89b72abed7393d9d428f1e336e3aa02e7ef6ce8)
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
[3.14] gh-72507: Document that imaplib does not verify TLS certificates by default (GH-152778) (GH-152863)
IMAP4_SSL() and IMAP4.starttls() do not verify the server certificate or
hostname unless a suitable ssl_context is passed.
(cherry picked from commit f3bf8abb8c0f4cb20bed3dc7d98eca4a2d668709)
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
[3.14] gh-79964: Clarify the imaplib fetch example in the docs (GH-152780) (GH-152860)
A FETCH response may contain additional or unsolicited data, so the
message is not always data[0][1].
(cherry picked from commit 50ae0be74988564fb09746a49eb4b8f8e740f3c3)
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
[3.14] gh-82183: Do not restart the busy IDLE shell when running without restart (GH-152745) (#152833)
gh-82183: Do not restart the busy IDLE shell when running without restart (GH-152745)
"Run... Customized" with "Restart shell" unchecked restarted the shell
anyway when it was busy executing code, killing any pending input. It now
reports that the shell is executing just once, not twice, and does not run.
(cherry picked from commit be449b1c6ad739a40a7da35a81317bb49f1f54ff)
[3.14] gh-75595: Do not save a blank int entry in IDLE Settings (GH-152743) (#152828)
gh-75595: Do not save a blank int entry in IDLE Settings (GH-152743)
gh-83653: Do not save a blank int entry in IDLE Settings
Blanking an integer entry wrote an empty string to the config file, which
caused an "invalid int value" warning when it was read back.
(cherry picked from commit 3428762f33c6c064998f5b0c385cdfd0f0c2e198)
[3.14] gh-65339: Save IDLE Shell and Output windows as text by default (GH-152742) (#152822)
gh-65339: Save IDLE Shell and Output windows as text by default (GH-152742)
Their content is not Python source, so the Save As dialog now lists text
files first and defaults to a ".txt" extension.
(cherry picked from commit efcfb1a4e0f4d0d0e33aefccd8373e116e98e3de)
[3.14] gh-80504: Always show the full search path in IDLE Find in Files (GH-152740) (#152811)
gh-80504: Always show the full search path in IDLE Find in Files (GH-152740)
The "In files:" field of the Find in Files dialog now always contains a
full directory path, so the grep output shows which directory was searched.
(cherry picked from commit f21f338f1f88352d50362271a7f38c01745a65da)
[3.14] gh-134300: Remove idlelib from the path of the IDLE user process (GH-152739) (#152808)
gh-134300: Remove idlelib from the path of the IDLE user process (GH-152739)
The idlelib directory ends up on sys.path when idle.py is run as a script,
and it was passed to the user process, where it let user code import
idlelib submodules as top-level modules, such as "import help".
(cherry picked from commit 3f5491a09223f7371b4b4c92225e8dfdf1c989b7)
[3.14] gh-89360: Fix ValueError in IDLE MultiCall event_delete (GH-152738) (#152803)
gh-89360: Fix ValueError in IDLE MultiCall event_delete (GH-152738)
Deleting a key binding for a sequence not bound to the virtual event no
longer raises a ValueError; the discrepancy is now ignored.
(cherry picked from commit ea7619faeacf310ba1b2ef41ad07966ce2aae11f)
[3.14] gh-71956: Fix IDLE Replace All searching up without wrap around (GH-152737) (#152800)
gh-71956: Fix IDLE Replace All searching up without wrap around (GH-152737)
When the search direction is "Up" and "Wrap around" is off, Replace All
replaced only the first match above the current position (and all matches
below it). It now replaces all matches from the start of the text down to
the current position, consistently with the "Up" direction.
(cherry picked from commit ef27e5b310feebb6068f9e798eee7069e1966fc9)
[3.14] gh-66331: Set correct WM_CLASS on X11 for IDLE windows (GH-152733) (#152795)
gh-66331: Set correct WM_CLASS on X11 for IDLE windows (GH-152733)
Set the WM_CLASS of IDLE's long-lived windows (window-list windows and
the stack viewers) to "Idle" instead of the default "Toplevel", so that
window managers group and label them correctly.
(cherry picked from commit 4e16b8d4eef162082c3d409f171802ceccb36521)
[3.14] gh-151626: Fix tests that fail when PYTHONPYCACHEPREFIX is set (GH-151952) (#152744)
Fix tests in test_compileall, test_import, test_importlib, test_py_compile, and test_inspect to neutralize PYTHONPYCACHEPREFIX
(cherry picked from commit 564c58c718bc3de9cf3d9bc20cdc06317411c1cd)
IDLE - move 3 toplevel fix_xyz functions to idlelb.util
Move idlelib functions run.fix_scaling, editor.fixwordbreaks
(as fix_word_breaks). All are used in at least 3 modules.
(cherry picked from commit 53ca52ddb0c2725a1c34e4eb2245339cd3dcaa84)
Co-authored-by: Terry Jan Reedy <tjreedy@udel.edu>
[3.14] gh-152502: Detect the curses mouse interface portably (GH-152705) (GH-152734)
The mouse interface (getmouse(), the BUTTON* constants, ...) was gated on the
ncurses-specific NCURSES_MOUSE_VERSION macro, so it was dropped on other curses
implementations that provide it, such as NetBSD curses and PDCurses.
Gate it instead on a configure capability probe or the PDCURSES macro. Probe
for getmouse() with its X/Open getmouse(MEVENT *) signature, since PDCurses
declares an incompatible getmouse(void) unless built for the ncurses mouse API,
which PDC_NCMOUSE now always selects.
[3.14] gh-152680: Detect container/VM in test.pythoninfo (GH-152668) (#152700)
gh-152680: Detect container/VM in test.pythoninfo (GH-152668)
On Apple, log the hardware model as "system.hardware".
* Log the "CI", "IMAGE_OS_VERSION" and "container"
environment variables.
* Add run_command() and first_line() functions.
(cherry picked from commit f1c536372199cfb9c2e7b360b7bf04208c9eee82)
Co-authored-by: Victor Stinner <vstinner@python.org>
[3.14] gh-151126: Fix a possible crash during the startup with no memory under `Py_STACKREF_DEBUG` (GH-152478) (#152678)
gh-151126: Fix a possible crash during the startup with no memory under `Py_STACKREF_DEBUG` (GH-152478)
(cherry picked from commit ecdef1773006529b0fea6639d0effeecbb41679c)
[3.14] gh-152635: Raise MemoryError when the lock allocation fails in `_interpchannels.create()` (GH-152642) (#152672)
gh-152635: Raise MemoryError when the lock allocation fails in `_interpchannels.create()` (GH-152642)
Previously, an allocation failure when creating
the lock for a channel in `_interpchannels` would trigger an assert.
Caused by `handle_channel_error` being passed an error code of -1
which is only allowed if an exception has been set.
(in this case, no exception was set)
`channelsmod_create` now forwards the error code from `channel_create`
which `handle_channel_error` already handled.
(cherry picked from commit b383aa6e1a8ea53fdeed88c71fbc34d8b2d1fde9)
Co-authored-by: Steve Stagg <stestagg@gmail.com> Co-authored-by: sobolevn <mail@sobolevn.me>
TurtleScreenBase._update() redraws with cv.update(), which also reprocesses
input events, so a handler that moves the turtle (such as
screen.ondrag(turtle.goto)) reenters _update() for every queued event until
the interpreter crashes. A reentrant _update() now only flushes drawing with
update_idletasks().
(cherry picked from commit 6f103fab178c07cbb5f701b8ad97e275b6eb6c4c)
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
[3.14] gh-103878: Document that the cancelled file dialog value is only falsy (GH-152429) (GH-152627)
Its exact type (empty string, tuple, list or None) varies between
platforms and Tk versions, so advise testing the result for truth.
(cherry picked from commit 9dd3340a623093b5d0ff054d5d42c370bb09221c)
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
[3.14] gh-69134: Wait until mapped in keyboard virtual-event tests (GH-152599) (GH-152606)
test_virtual_events and test_selection_event generate key events after
focus_force(). On Windows these are only delivered once the toplevel is
mapped, so they could be dropped and the test fail. Wait until the
widget is mapped, as the other GUI tests already do.
(cherry picked from commit be4eebb8386d07f360dd835a0ecb4a1eb6385736)
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
[3.14] gh-152228: Fix an assertion failure in `str.replace` under a limited memory case (GH-152229) (#152616)
gh-152228: Fix an assertion failure in `str.replace` under a limited memory case (GH-152229)
(cherry picked from commit c5043dce1c6743682d93c71e937b95e7d27e0b35)
set_escdelay(), set_tabsize() and the ESCDELAY and TABSIZE variables were
gated only by the ncurses-specific NCURSES_EXT_FUNCS macro, which excluded
them when building against other curses implementations such as NetBSD curses
even when they provided them. Detect each with a configure capability probe
and gate on HAVE_CURSES_*.
(cherry picked from commit 0635e55b47e306aa6cc0610105775849b0699e2c)
(cherry picked from commit 2a4ffa6f2128308ba5ba81a7c8912fb781a271e0)
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
The name parameter of Misc.wait_variable(), setvar() and getvar() and the
value parameter of setvar() should not be optional. Their default values
('PY_VAR' and '1') are not meaningful and should not be advertised.
(cherry picked from commit e134a1aa36027fb3c8bd9493028072365b19479b)
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
[3.14] gh-133031: Support non-ASCII characters in curses.textpad.Textbox (GH-152451) (GH-152468)
Textbox mangled non-ASCII characters of an 8-bit locale encoding: it masked
reads with curses.ascii.ascii(), which clears the 8th bit, and tested input
with curses.ascii.isprint(), which rejects bytes above 127.
Decode cells and input bytes with the window's encoding instead. Cells are
read with instr() and the lower-right cell is written as text, since inch() and
insch() with an int byte mishandle a non-ASCII character on a wide build. This
uses only the byte-oriented curses API, so it works without wide-character
support.
(cherry picked from commit c253f0c14b046cf79881d0602620a3b2a15c2316)
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
[3.14] gh-85320: Use UTF-8 for IDLE configuration and breakpoint files (GH-152475) (GH-152556)
They were read and written using the locale encoding, which could corrupt
non-ASCII paths and made them non-portable.
(cherry picked from commit f6e904e1a666cb1e5664750b1c3d8f89cba3a769)
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
[3.14] gh-87577: Document that wm_manage does not accept ttk widgets (GH-152532) (GH-152544)
wm_manage() works only with the classic tkinter Frame, LabelFrame and
Toplevel widgets, not their tkinter.ttk counterparts.
(cherry picked from commit 2670cb062c9ec31cd6df7be645f929a8398601c7)
Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>