-
Notifications
You must be signed in to change notification settings - Fork 386
Description
Summary
On Windows 2019 server, when running a native console program (e.g. mingw-w64-ucrt-x86_64-python) in the UCRT64 mintty shell, long lines that contain ANSI SGR color codes (ESC[...m) wrap too early.
The wrapping appears to be done on raw byte length, counting the characters in \x1b[...m as visible columns, but then the rendering treats them as zero-width formatting. This can cause the wrap to happen inside an SGR sequence, so m or 0m appears at the beginning of the next line.
The same text, when printed with MSYS bash + printf, does not show this behavior: wrapping correctly respects the visible length and treats SGR as zero-width.
So:
- MSYS/printf → mintty: ✅ correct wrapping
- native console app (Python) → console/pty bridge → mintty: ❌ wraps too early, splits SGR (this issue does not seem to exist on Windows 10 or 11)
Environment
OS
- Windows Server 2019: Version 1809 OS Build 17763.7792
Terminal
- MSYS2 UCRT64 mintty (started from the “MSYS2 UCRT64” shortcut)
MSYS2:
- MSYSTEM=UCRT64
- TERM=xterm-256color
- COLUMNS=80
- tput cols = 80
Python
$ pacman -Qi mingw-w64-ucrt-x86_64-python
Name : mingw-w64-ucrt-x86_64-python
Version : 3.12.12-1
Description : A high-level scripting language (mingw-w64)
Architecture : any
URL : https://www.python.org/
Licenses : spdx:PSF-2.0
Groups : None
Provides : mingw-w64-ucrt-x86_64-python3
mingw-w64-ucrt-x86_64-python3.12
Depends On : mingw-w64-ucrt-x86_64-cc-libs mingw-w64-ucrt-x86_64-expat
mingw-w64-ucrt-x86_64-bzip2 mingw-w64-ucrt-x86_64-libffi
mingw-w64-ucrt-x86_64-mpdecimal
mingw-w64-ucrt-x86_64-ncurses mingw-w64-ucrt-x86_64-openssl
mingw-w64-ucrt-x86_64-sqlite3 mingw-w64-ucrt-x86_64-tcl
mingw-w64-ucrt-x86_64-tk mingw-w64-ucrt-x86_64-zlib
mingw-w64-ucrt-x86_64-xz mingw-w64-ucrt-x86_64-tzdata
Optional Deps : None
Required By : None
Optional For : None
Conflicts With : mingw-w64-ucrt-x86_64-python3
mingw-w64-ucrt-x86_64-python3.12
mingw-w64-ucrt-x86_64-python2<2.7.16-7
Replaces : mingw-w64-ucrt-x86_64-python3
mingw-w64-ucrt-x86_64-python3.12
Installed Size : 185.99 MiB
Packager : CI (msys2/msys2-autobuild/55384653/18406062571)
Build Date : Fri, 10 Oct, 2025 14:42:57
Install Date : Wed, 3 Dec, 2025 9:47:09
Install Reason : Explicitly installed
Install Script : No
Validated By : SHA-256 Sum Signature
$ mintty --version
mintty '3.8.1' 2025-09-18_06:11 (Msys-x86_64)
© 2025 Thomas Wolff, Andy Koppe
License GPLv3+: GNU GPL version 3 or later
There is no warranty, to the extent permitted by law.
Repro 1 - bash + printf - works correctly
#!/usr/bin/env bash
# ansi_wrap_test.sh
# Report terminal width
cols=$(tput cols)
echo "cols = $cols"
# Plain (no color) version of the line
plain='[DemoProcess1] [process1] [MainThread] [A Custom Task] [INF] Detailed step 1'
echo "plain_len = ${#plain}"
echo
echo "Plain line:"
printf '%s\n' "$plain"
echo
# ANSI color codes
blue=$'\033[34m'
bold=$'\033[1m'
green=$'\033[32m'
reset=$'\033[0m'
# Colored version
colored="[DemoProcess1] [process1] [MainThread] ${blue}${bold}[A Custom Task]${reset} ${green}[INF]${reset} Detailed step 1"
raw_len=${#colored}
# Visible length: strip SGR sequences and measure
visible=$(printf '%s' "$colored" | sed -E 's/\x1b\[[0-9;]*m//g')
visible_len=${#visible}
echo "raw_len = $raw_len"
echo "visible_len = $visible_len"
echo
echo "Colored line:"
printf '%s\n' "$colored"
Example output on my machine:
cols = 80
plain_len = 76
Plain line:
[DemoProcess1] [process1] [MainThread] [A Custom Task] [INF] Detailed step 1
raw_len = 98
visible_len = 76
Colored line:
[DemoProcess1] [process1] [MainThread] [A Custom Task] [INF] Detailed step 1
Repro 2 – native Python (wraps too early / splits ESC[0m)
In the same UCRT64 mintty window, install MSYS2 UCRT Python with pacman: -S --needed mingw-w64-ucrt-x86_64-python
Create test.py:
import re
import shutil
BLUE = "\x1b[34m"
BOLD = "\x1b[1m"
GREEN = "\x1b[32m"
RESET = "\x1b[0m"
def visible_len(s: str) -> int:
# strip SGR color codes
return len(re.sub(r'\x1b\[[0-9;]*m', '', s))
def run_test():
task_name = "A Custom Task"
log_level_short = f"{GREEN}[INF]{RESET}"
msg = "Detailed step 1"
text = (
"[DemoProcess1] [process1] [MainThread] "
f"{BLUE}{BOLD}[{task_name}]{RESET} "
f"{log_level_short} "
f"{msg}"
)
max_line_length = shutil.get_terminal_size(fallback=(80, 20)).columns
text_len = len(text)
text_visible_len = visible_len(text)
print(f"text_length = {text_len}")
print(f"text_visible_length = {text_visible_len}")
print(f"max_line_length = {max_line_length}")
print(text)
if __name__ == "__main__":
run_test()
Example output:
text_length = 98
text_visible_length = 76
max_line_length = 80
[DemoProcess1] [process1] [MainThread] [A Custom Task] [INF]
0m Detailed step 1
The 0m is the tail of the \x1b[0m SGR reset sequence. The wrap is happening inside the escape sequence, which suggests that the wrapping decision is based on the raw byte count (including ESC[, digits, m) instead of on visible columns.
Expected behavior
For both MSYS printf and native-console programs running under UCRT64/mintty:
- Wrapping should be based on visible columns.
- ANSI SGR sequences (ESC[...m) should be treated as zero-width for wrapping.
- The line above should not wrap, since its visible length (76) is less than the terminal width (80).
- ANSI escape sequences should not be split across lines (no stray m / 0m at line starts)