msys2 / msys2/msys2.github.io

Native console apps with ANSI color wrap too early in MSYS2 UCRT64 mintty (wrap splits ESC[…m sequences) on Windows Server 2019

Open
#411 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
CSS
Stars
3.3k
Forks
446
Avg merge
2h 16m
Merged PRs (30d)
1

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)

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Reproduce the difference with ansi_wrap_test.sh and test.py in the UCRT64 mintty environment, comparing MSYS printf with native Python output. Trace the native-console/pty bridge involved in the failing path; done means visible-column wrapping matches the terminal width and ANSI SGR sequences are not split across lines.

Written by the indexing model from the issue text.

Assessment

Tech stack
bash, python
Domain
cli, operating-systems
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.