Skip to content

Commit a6b1cf4

Browse files
mishushakovanxkhn
andauthored
fix(python-sdk): strip colon-separated SGR escape codes in build logs (#1522)
### What Cherry-picks the fix from #1519. `strip_ansi_escape_codes` in the Python SDK only matched semicolon-separated CSI parameters, so colon-separated SGR sequences leaked literal escape garbage into template build-log messages. This widens the parameter class from `;` to `[;:]` so colon-separated sequences are stripped too, matching the JS SDK's `stripAnsi`. Modern terminals emit colon-separated SGR sequences: - 256-color: `\x1b[38:5:82m` - truecolor: `\x1b[38:2::255:0:0m` - curly underline: `\x1b[4:3m` The two SDKs share one source (chalk/ansi-regex) and the JS twin was already updated to support colons (`packages/js-sdk/src/utils.ts:95`, comment: "supports ; and :"); the Python port lagged behind. `strip_ansi_escape_codes` is consumed by `LogEntry.__post_init__` (`packages/python-sdk/e2b/template/logger.py`), so the leftover escape bytes showed up in Python build logs only. ### The one-line fix ```python # packages/python-sdk/e2b/template/utils.py:319 - r"(?:(?:\d{1,4}(?:;\d{0,4})*)?[\dA-PR-TZcf-nq-uy=><~]))", + r"(?:(?:\d{1,4}(?:[;:]\d{0,4})*)?[\dA-PR-TZcf-nq-uy=><~]))", ``` ### Usage example (before / after) ```python from e2b.template.utils import strip_ansi_escape_codes # 256-color, colon-separated strip_ansi_escape_codes("\x1b[38:5:82mX\x1b[0m") # before: ":5:82mX" after: "X" # truecolor, colon-separated strip_ansi_escape_codes("\x1b[38:2::255:0:0mRED\x1b[0m") # before: ":2::255:0:0mRED" after: "RED" # semicolon variants already worked and still do strip_ansi_escape_codes("\x1b[38;5;82mX\x1b[0m") # "X" (unchanged) ``` ### Tests Unit tests at `packages/python-sdk/tests/shared/template/utils/test_strip_ansi_escape_codes.py` (no API key / sandbox): colon-256, colon-truecolor, curly-underline, plus basic/semicolon regressions. All 7 pass locally. ### Changeset `.changeset/python-strip-ansi-colon.md` (patch on `@e2b/python-sdk`). ### Notes Original PR: #1519 (by @anxkhn). Opened against a fresh branch off `main` per request, rather than merging #1519 directly. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Anas Khan <83116240+anxkhn@users.noreply.github.com>
1 parent 2b7dd17 commit a6b1cf4

3 files changed

Lines changed: 41 additions & 1 deletion

File tree

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
"@e2b/python-sdk": patch
3+
---
4+
5+
fix(python-sdk): strip colon-separated SGR escape codes in build logs
6+
7+
`strip_ansi_escape_codes` now strips CSI parameters that use colons as well as
8+
semicolons, matching the JavaScript SDK's `stripAnsi`. Modern terminals emit
9+
colon-separated SGR sequences (e.g. 256-color `\x1b[38:5:82m`, truecolor
10+
`\x1b[38:2::r:g:bm`, and curly underlines `\x1b[4:3m`), which previously leaked
11+
literal escape garbage into template build log messages on the Python side.

packages/python-sdk/e2b/template/utils.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ def strip_ansi_escape_codes(text: str) -> str:
318318
st = r"(?:\u0007|\u001B\u005C|\u009C)"
319319
pattern = [
320320
rf"[\u001B\u009B][\[\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\d/#&.:=?%@~_]+)*|[a-zA-Z\d]+(?:;[-a-zA-Z\d/#&.:=?%@~_]*)*)?{st})",
321-
r"(?:(?:\d{1,4}(?:;\d{0,4})*)?[\dA-PR-TZcf-nq-uy=><~]))",
321+
r"(?:(?:\d{1,4}(?:[;:]\d{0,4})*)?[\dA-PR-TZcf-nq-uy=><~]))",
322322
]
323323
ansi_escape = re.compile("|".join(pattern), re.UNICODE)
324324
return ansi_escape.sub("", text)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
from e2b.template.utils import strip_ansi_escape_codes
2+
3+
4+
def test_strips_basic_sgr_color():
5+
assert strip_ansi_escape_codes("\x1b[31mred\x1b[0m") == "red"
6+
7+
8+
def test_strips_semicolon_separated_params():
9+
assert strip_ansi_escape_codes("\x1b[1;31;42mhi\x1b[0m") == "hi"
10+
11+
12+
def test_strips_semicolon_256_color():
13+
assert strip_ansi_escape_codes("\x1b[38;5;82mX\x1b[0m") == "X"
14+
15+
16+
def test_strips_colon_256_color():
17+
assert strip_ansi_escape_codes("\x1b[38:5:82mX\x1b[0m") == "X"
18+
19+
20+
def test_strips_colon_truecolor():
21+
assert strip_ansi_escape_codes("\x1b[38:2::255:0:0mRED\x1b[0m") == "RED"
22+
23+
24+
def test_strips_colon_curly_underline():
25+
assert strip_ansi_escape_codes("\x1b[4:3mX\x1b[0m") == "X"
26+
27+
28+
def test_leaves_plain_text_unchanged():
29+
assert strip_ansi_escape_codes("no escape codes here") == "no escape codes here"

0 commit comments

Comments
 (0)