René's URL Explorer Experiment


Title: fix(out): reconfigure stdout to UTF-8 on any non-UTF-8 terminal by bearomorphism · Pull Request #1975 · commitizen-tools/commitizen · GitHub

Open Graph Title: fix(out): reconfigure stdout to UTF-8 on any non-UTF-8 terminal by bearomorphism · Pull Request #1975 · commitizen-tools/commitizen

X Title: fix(out): reconfigure stdout to UTF-8 on any non-UTF-8 terminal by bearomorphism · Pull Request #1975 · commitizen-tools/commitizen

Description: Description Closes #956. Why Commitizen emits Unicode characters that fall outside the Latin-1 repertoire — most visibly the rocket emoji (\U0001f680) in out.success("Configuration complete 🚀") and the typographic apostrophe (\u2019) scattered throughout the conventional-commits info text. When Python's print() encodes those characters through sys.stdout, it uses whatever encoding the operating system assigned to the terminal. On a system whose LANG is set to de_CH.ISO8859-1 (or any other non-UTF-8 locale), that encoding is Latin-1, which cannot represent either character, so Python raises UnicodeEncodeError and crashes the command mid-output. The original guard in commitizen/out.py:7-9 only reconfigures sys.stdout to UTF-8 when sys.platform == "win32". Linux and macOS users on legacy ISO-8859-1 locales — the reporter ran AlmaLinux 8.9 and macOS Ventura 13.6.3, both with LANG=de_CH.ISO8859-1 — received no such reconfiguration, so every print() call that contained an out-of-range character exploded. Maintainer @Lee-W acknowledged the reproduction in the issue thread; a verification comment on the triage audit (#1964) confirmed the code path is unchanged in master (v4.15.1). The fix extracts the reconfiguration into a small _ensure_utf8_stdout() helper that is encoding-agnostic and platform-agnostic. It is called once at module-import time so that every subsequent print() in every command — write, line, success, info, warn — is already operating on a UTF-8 stream before the first byte is written. What changed File Change commitizen/out.py Drop the sys.platform == "win32" guard (lines 7–9); add _ensure_utf8_stdout() helper; call it unconditionally on sys.stdout at module import tests/test_out.py New file — 6 unit tests: UTF-8 no-op, dashless-alias no-op (UTF8), ISO-8859-1 reconfigure, cp1252 reconfigure, non-TextIOWrapper skip, and end-to-end emoji write-through How it works Encoding normalisation before comparison. The helper computes (stream.encoding or "").lower().replace("-", "").replace("_", "") before comparing against "utf8". This collapses the alias zoo — UTF-8, utf_8, UTF8 — into a single canonical string, so the no-op path is taken for all valid UTF-8 spelling variants without maintaining an allowlist. errors="replace" as a safety net. Even after reconfiguration, a terminal that physically can't render a byte sequence (e.g., a very old xterm with a broken locale) will receive a ? placeholder rather than raising a second UnicodeEncodeError. Crashing mid-output after a user has already answered several cz init prompts is a worse experience than a one-character substitution. Module-level call, not per-print wrapping. _ensure_utf8_stdout(sys.stdout) executes once when commitizen.out is first imported. Because every CLI command imports out before writing anything, the reconfiguration is guaranteed to be in place for the full lifetime of the process — no per-function guard is needed. isinstance(stream, io.TextIOWrapper) guard. Pipes, io.StringIO instances, and pytest's stdout-capture objects are not TextIOWrapper subclasses and do not expose reconfigure(). Checking the type before calling keeps the helper safe in test environments and in any context where sys.stdout has been replaced by a non-standard object. AttributeError / ValueError are swallowed silently. A stream whose underlying buffer is already closed raises ValueError; a stream backed by a read-only file descriptor raises AttributeError. Neither case should crash the import. The # pragma: no cover annotation marks these as genuine safety-net branches rather than untested logic. Backward compatibility Windows behaviour is unchanged: the new helper fires on any platform when encoding is not UTF-8, including cp1252 (the historical Windows case), so the reconfiguration that previously lived behind the win32 guard still happens. Callers that already have a UTF-8 sys.stdout (the common case on modern Linux/macOS) hit the early-return branch — no reconfigure() call, no observable difference. All existing tests pass; tests/test_out.py is a new file, so there are no test regressions. No CLI flags, exit codes, or configuration keys are added or removed. Checklist I have read the contributing guidelines Was generative AI tooling used to co-author this PR? Yes (please specify the tool below) Generated-by: Claude following the guidelines Code Changes Add test cases to all the changes you introduce Run uv run poe all locally to ensure this change passes linter check and tests Manually test the changes (see "Steps to Test" below) Update the documentation for the changes Expected Behavior Scenario Outcome LANG=de_CH.ISO8859-1 cz info on Linux/macOS Prints full info text without UnicodeEncodeError; \u2019 (typographic apostrophe) passes through or is replaced by ? LANG=de_CH.ISO8859-1 cz init completes successfully Prints Configuration complete 🚀 without crashing on \U0001f680 LANG=en_US.UTF-8 cz info (UTF-8 terminal) Behaviour unchanged — _ensure_utf8_stdout hits the early-return branch and calls no reconfigure() Non-TextIOWrapper stdout (e.g. piped, pytest capture) _ensure_utf8_stdout returns immediately; no AttributeError Steps to Test This Pull Request git fetch fork fix/956-cross-platform-stdout-utf8 git checkout fork/fix/956-cross-platform-stdout-utf8 # 1. Targeted regression test. uv run pytest tests/test_out.py -v # 2. Reproduce-the-bug-then-verify-the-fix sequence. # Simulate a Latin-1 stdout without needing to change the OS locale: python - << 'EOF' import io, sys # Replace stdout with a Latin-1 TextIOWrapper — exactly what the OS gives # you when LANG=de_CH.ISO8859-1. sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="latin-1") # Re-import out so _ensure_utf8_stdout() runs against our stub stream. import importlib, commitizen.out as _out importlib.reload(_out) # Before the fix this line crashed with UnicodeEncodeError. _out.write("Conventional commits info: \u2019") _out.success("Configuration complete \U0001f680") print("OK — no UnicodeEncodeError", file=sys.__stdout__) EOF # On a real Linux/macOS machine with a non-UTF-8 locale: # LANG=de_CH.ISO8859-1 cz info # previously crashed # LANG=de_CH.ISO8859-1 cz init # previously crashed at success message Additional Context This fix emerged from the cross-cutting audit in #1964, which confirmed the Windows-only guard in commitizen/out.py:7-9 was the sole cause and that the code path is unchanged in master (v4.15.1). The _ensure_utf8_stdout helper was designed to be testable without monkey-patching the real sys.stdout and reimporting the module, which is why it accepts an arbitrary stream argument rather than hard-coding sys.stdout inside the function body.

Open Graph Description: Description Closes #956. Why Commitizen emits Unicode characters that fall outside the Latin-1 repertoire — most visibly the rocket emoji (\U0001f680) in out.success("Configuration complete 🚀&...

X Description: Description Closes #956. Why Commitizen emits Unicode characters that fall outside the Latin-1 repertoire — most visibly the rocket emoji (\U0001f680) in out.success(&quot;Configuration complet...

Opengraph URL: https://github.com/commitizen-tools/commitizen/pull/1975

X: @github

direct link

Domain: github.com

route-pattern/:user_id/:repository/pull/:id/files(.:format)
route-controllerpull_requests
route-actionfiles
fetch-noncev2:29b0546a-ac09-a2b9-da63-ae91b154f389
current-catalog-service-hashae870bc5e265a340912cde392f23dad3671a0a881730ffdadd82f2f57d81641b
request-id8912:1312C8:2B128:3C0B2:6A50EA87
html-safe-nonce1497af432f411cecc982036bb77a6b5a01b508b6c1d84b031b69f4a0df55b27c
visitor-payloadeyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiI4OTEyOjEzMTJDODoyQjEyODozQzBCMjo2QTUwRUE4NyIsInZpc2l0b3JfaWQiOiIxMDk2NjkyMDg1NDgyNDAwMDciLCJyZWdpb25fZWRnZSI6ImlhZCIsInJlZ2lvbl9yZW5kZXIiOiJpYWQifQ==
visitor-hmac60edc61fca6b5afdf9444a842b7ba392712b1becb6753300d78c26170d2253f8
hovercard-subject-tagpull_request:3654744590
github-keyboard-shortcutsrepository,pull-request-list,pull-request-conversation,pull-request-files-changed,copilot
google-site-verificationApib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I
octolytics-urlhttps://collector.github.com/github/collect
analytics-location///pull_requests/show/files
fb:app_id1401488693436528
apple-itunes-appapp-id=1477376905, app-argument=https://github.com/commitizen-tools/commitizen/pull/1975/files
twitter:imagehttps://avatars.githubusercontent.com/u/26526132?s=400&v=4
twitter:cardsummary_large_image
og:imagehttps://avatars.githubusercontent.com/u/26526132?s=400&v=4
og:image:altDescription Closes #956. Why Commitizen emits Unicode characters that fall outside the Latin-1 repertoire — most visibly the rocket emoji (\U0001f680) in out.success("Configuration complete 🚀&...
og:site_nameGitHub
og:typeobject
hostnamegithub.com
expected-hostnamegithub.com
None72b13e0e8d0319acdd27a145cfe73f4f06c791713d0ac4690e41fb68ad28cac0
turbo-cache-controlno-preview
diff-viewunified
go-importgithub.com/commitizen-tools/commitizen git https://github.com/commitizen-tools/commitizen.git
octolytics-dimension-user_id62252524
octolytics-dimension-user_logincommitizen-tools
octolytics-dimension-repository_id106127589
octolytics-dimension-repository_nwocommitizen-tools/commitizen
octolytics-dimension-repository_publictrue
octolytics-dimension-repository_is_forkfalse
octolytics-dimension-repository_network_root_id106127589
octolytics-dimension-repository_network_root_nwocommitizen-tools/commitizen
turbo-body-classeslogged-out env-production page-responsive full-width
disable-turbotrue
browser-stats-urlhttps://api.github.com/_private/browser/stats
browser-errors-urlhttps://api.github.com/_private/browser/errors
releasefb19d617b534959801b4b7453938ecf1dfa22131
ui-targetfull
theme-color#1e2327
color-schemelight dark

Links:

Skip to contenthttps://github.com/commitizen-tools/commitizen/pull/1975/files#start-of-content
https://github.com/
Sign in https://github.com/login?return_to=https%3A%2F%2Fgithub.com%2Fcommitizen-tools%2Fcommitizen%2Fpull%2F1975%2Ffiles
GitHub CopilotWrite better code with AIhttps://github.com/features/copilot
GitHub Copilot appDirect agents from issue to mergehttps://github.com/features/ai/github-app
MCP RegistryNewIntegrate external toolshttps://github.com/mcp
ActionsAutomate any workflowhttps://github.com/features/actions
CodespacesInstant dev environmentshttps://github.com/features/codespaces
IssuesPlan and track workhttps://github.com/features/issues
Code ReviewManage code changeshttps://github.com/features/code-review
GitHub Advanced SecurityFind and fix vulnerabilitieshttps://github.com/security/advanced-security
Code securitySecure your code as you buildhttps://github.com/security/advanced-security/code-security
Secret protectionStop leaks before they starthttps://github.com/security/advanced-security/secret-protection
Why GitHubhttps://github.com/why-github
Documentationhttps://docs.github.com
Bloghttps://github.blog
Changeloghttps://github.blog/changelog
Marketplacehttps://github.com/marketplace
View all featureshttps://github.com/features
Enterpriseshttps://github.com/enterprise
Small and medium teamshttps://github.com/team
Startupshttps://github.com/enterprise/startups
Nonprofitshttps://github.com/solutions/industry/nonprofits
App Modernizationhttps://github.com/solutions/use-case/app-modernization
DevSecOpshttps://github.com/solutions/use-case/devsecops
DevOpshttps://github.com/solutions/use-case/devops
CI/CDhttps://github.com/solutions/use-case/ci-cd
View all use caseshttps://github.com/solutions/use-case
Healthcarehttps://github.com/solutions/industry/healthcare
Financial serviceshttps://github.com/solutions/industry/financial-services
Manufacturinghttps://github.com/solutions/industry/manufacturing
Governmenthttps://github.com/solutions/industry/government
View all industrieshttps://github.com/solutions/industry
View all solutionshttps://github.com/solutions
AIhttps://github.com/resources/articles?topic=ai
Software Developmenthttps://github.com/resources/articles?topic=software-development
DevOpshttps://github.com/resources/articles?topic=devops
Securityhttps://github.com/resources/articles?topic=security
View all topicshttps://github.com/resources/articles
Customer storieshttps://github.com/customer-stories
Events & webinarshttps://github.com/resources/events
Ebooks & reportshttps://github.com/resources/whitepapers
Business insightshttps://github.com/solutions/executive-insights
GitHub Skillshttps://skills.github.com
Documentationhttps://docs.github.com
Customer supporthttps://support.github.com
Community forumhttps://github.com/orgs/community/discussions
Trust centerhttps://github.com/trust-center
Partnershttps://github.com/partners
View all resourceshttps://github.com/resources
GitHub SponsorsFund open source developershttps://github.com/open-source/sponsors
Security Labhttps://securitylab.github.com
Maintainer Communityhttps://maintainers.github.com
Acceleratorhttps://github.com/open-source/accelerator
GitHub Starshttps://stars.github.com
Archive Programhttps://archiveprogram.github.com
Topicshttps://github.com/topics
Trendinghttps://github.com/trending
Collectionshttps://github.com/collections
Enterprise platformAI-powered developer platformhttps://github.com/enterprise
GitHub Advanced SecurityEnterprise-grade security featureshttps://github.com/security/advanced-security
Copilot for BusinessEnterprise-grade AI featureshttps://github.com/features/copilot/copilot-business
Premium SupportEnterprise-grade 24/7 supporthttps://github.com/enterprise/premium-support
Pricinghttps://github.com/pricing
Search syntax tipshttps://docs.github.com/search-github/github-code-search/understanding-github-code-search-syntax
documentationhttps://docs.github.com/search-github/github-code-search/understanding-github-code-search-syntax
Sign in https://github.com/login?return_to=https%3A%2F%2Fgithub.com%2Fcommitizen-tools%2Fcommitizen%2Fpull%2F1975%2Ffiles
Sign up https://github.com/signup?ref_cta=Sign+up&ref_loc=header+logged+out&ref_page=%2F%3Cuser-name%3E%2F%3Crepo-name%3E%2Fpull_requests%2Fshow%2Ffiles&source=header-repo&source_repo=commitizen-tools%2Fcommitizen
Reloadhttps://github.com/commitizen-tools/commitizen/pull/1975/files
Reloadhttps://github.com/commitizen-tools/commitizen/pull/1975/files
Reloadhttps://github.com/commitizen-tools/commitizen/pull/1975/files
Please reload this pagehttps://github.com/commitizen-tools/commitizen/pull/1975/files
commitizen-tools https://github.com/commitizen-tools
commitizenhttps://github.com/commitizen-tools/commitizen
Please reload this pagehttps://github.com/commitizen-tools/commitizen/pull/1975/files
Notifications https://github.com/login?return_to=%2Fcommitizen-tools%2Fcommitizen
Fork 344 https://github.com/login?return_to=%2Fcommitizen-tools%2Fcommitizen
Star 3.5k https://github.com/login?return_to=%2Fcommitizen-tools%2Fcommitizen
Code https://github.com/commitizen-tools/commitizen
Issues 116 https://github.com/commitizen-tools/commitizen/issues
Pull requests 45 https://github.com/commitizen-tools/commitizen/pulls
Discussions https://github.com/commitizen-tools/commitizen/discussions
Actions https://github.com/commitizen-tools/commitizen/actions
Projects https://github.com/commitizen-tools/commitizen/projects
Security and quality 0 https://github.com/commitizen-tools/commitizen/security
Insights https://github.com/commitizen-tools/commitizen/pulse
Code https://github.com/commitizen-tools/commitizen
Issues https://github.com/commitizen-tools/commitizen/issues
Pull requests https://github.com/commitizen-tools/commitizen/pulls
Discussions https://github.com/commitizen-tools/commitizen/discussions
Actions https://github.com/commitizen-tools/commitizen/actions
Projects https://github.com/commitizen-tools/commitizen/projects
Security and quality https://github.com/commitizen-tools/commitizen/security
Insights https://github.com/commitizen-tools/commitizen/pulse
Sign up for GitHub https://github.com/signup?return_to=%2Fcommitizen-tools%2Fcommitizen%2Fissues%2Fnew%2Fchoose
terms of servicehttps://docs.github.com/terms
privacy statementhttps://docs.github.com/privacy
Sign inhttps://github.com/login?return_to=%2Fcommitizen-tools%2Fcommitizen%2Fissues%2Fnew%2Fchoose
bearomorphismhttps://github.com/bearomorphism
commitizen-tools:masterhttps://github.com/commitizen-tools/commitizen/tree/master
bearomorphism:fix/956-cross-platform-stdout-encodinghttps://github.com/bearomorphism/commitizen/tree/fix/956-cross-platform-stdout-encoding
Conversation 6 https://github.com/commitizen-tools/commitizen/pull/1975
Commits 3 https://github.com/commitizen-tools/commitizen/pull/1975/commits
Checks 20 https://github.com/commitizen-tools/commitizen/pull/1975/checks
Files changed https://github.com/commitizen-tools/commitizen/pull/1975/files
Please reload this pagehttps://github.com/commitizen-tools/commitizen/pull/1975/files
fix(out): reconfigure stdout to UTF-8 on any non-UTF-8 terminal https://github.com/commitizen-tools/commitizen/pull/1975/files#top
Show all changes 3 commits https://github.com/commitizen-tools/commitizen/pull/1975/files
e3a44d5 docs(cli/screenshots): update CLI screenshots github-actions[bot] May 9, 2026 https://github.com/commitizen-tools/commitizen/pull/1975/commits/e3a44d546f7a82fcd17266a4f6c067bae6b35fac
5885120 ci: retrigger workflows bearomorphism May 9, 2026 https://github.com/commitizen-tools/commitizen/pull/1975/commits/58851200d4941ea92dab682fbc2baa10d71aa67c
4606a51 docs(out): clarify _ensure_utf8_stdout docstring and test comments bearomorphism May 9, 2026 https://github.com/commitizen-tools/commitizen/pull/1975/commits/4606a5198d9432f6be5b37a34a04727df70d4f23
Clear filters https://github.com/commitizen-tools/commitizen/pull/1975/files
Please reload this pagehttps://github.com/commitizen-tools/commitizen/pull/1975/files
Please reload this pagehttps://github.com/commitizen-tools/commitizen/pull/1975/files
out.py https://github.com/commitizen-tools/commitizen/pull/1975/files#diff-0a469ea5461bc40166e6c6ad8e336cc81247224effdf19e3fdb195157e5d07e2
bump.gif https://github.com/commitizen-tools/commitizen/pull/1975/files#diff-3f4ae524317062d94d47d4e3708bc770dc3837d79c44156c9b144094e2d10160
commit.gif https://github.com/commitizen-tools/commitizen/pull/1975/files#diff-249010e3b825b7a77bc456b8a19d8528780b54567607d6726ceda966a36bfe76
init.gif https://github.com/commitizen-tools/commitizen/pull/1975/files#diff-b4bf63a82e50fbaccc92d7e5a303e3fd55737a01b2b58856ebdb263151cdabc4
shortcut_custom.gif https://github.com/commitizen-tools/commitizen/pull/1975/files#diff-2b3173b3a421fb573812dfe4bc63534de5105d0cf1a82a647392da5cd544ba7e
shortcut_default.gif https://github.com/commitizen-tools/commitizen/pull/1975/files#diff-0205dc25993d8a43aa234cfba3dd2a54ea1e3d336274e77584d09175219e1a12
test_out.py https://github.com/commitizen-tools/commitizen/pull/1975/files#diff-6c1d56366ace4ba541ea26d25affc315d272fe8cf0872a891b4c411a99ffc6f3
commitizen/out.pyhttps://github.com/commitizen-tools/commitizen/pull/1975/files#diff-0a469ea5461bc40166e6c6ad8e336cc81247224effdf19e3fdb195157e5d07e2
View file https://github.com/commitizen-tools/commitizen/blob/4606a5198d9432f6be5b37a34a04727df70d4f23/commitizen/out.py
Open in desktop https://desktop.github.com
https://github.co/hiddenchars
https://github.com/commitizen-tools/commitizen/pull/1975/{{ revealButtonHref }}
https://github.com/commitizen-tools/commitizen/pull/1975/files#diff-0a469ea5461bc40166e6c6ad8e336cc81247224effdf19e3fdb195157e5d07e2
https://github.com/commitizen-tools/commitizen/pull/1975/files#diff-0a469ea5461bc40166e6c6ad8e336cc81247224effdf19e3fdb195157e5d07e2
docs/images/cli_interactive/bump.gifhttps://github.com/commitizen-tools/commitizen/pull/1975/files#diff-3f4ae524317062d94d47d4e3708bc770dc3837d79c44156c9b144094e2d10160
View file https://github.com/commitizen-tools/commitizen/blob/4606a5198d9432f6be5b37a34a04727df70d4f23/docs/images/cli_interactive/bump.gif
Open in desktop https://desktop.github.com
Reload?https://github.com/commitizen-tools/commitizen/pull/1975/files
docs/images/cli_interactive/commit.gifhttps://github.com/commitizen-tools/commitizen/pull/1975/files#diff-249010e3b825b7a77bc456b8a19d8528780b54567607d6726ceda966a36bfe76
View file https://github.com/commitizen-tools/commitizen/blob/4606a5198d9432f6be5b37a34a04727df70d4f23/docs/images/cli_interactive/commit.gif
Open in desktop https://desktop.github.com
Reload?https://github.com/commitizen-tools/commitizen/pull/1975/files
docs/images/cli_interactive/init.gifhttps://github.com/commitizen-tools/commitizen/pull/1975/files#diff-b4bf63a82e50fbaccc92d7e5a303e3fd55737a01b2b58856ebdb263151cdabc4
View file https://github.com/commitizen-tools/commitizen/blob/4606a5198d9432f6be5b37a34a04727df70d4f23/docs/images/cli_interactive/init.gif
Open in desktop https://desktop.github.com
Reload?https://github.com/commitizen-tools/commitizen/pull/1975/files
docs/images/cli_interactive/shortcut_custom.gifhttps://github.com/commitizen-tools/commitizen/pull/1975/files#diff-2b3173b3a421fb573812dfe4bc63534de5105d0cf1a82a647392da5cd544ba7e
View file https://github.com/commitizen-tools/commitizen/blob/4606a5198d9432f6be5b37a34a04727df70d4f23/docs/images/cli_interactive/shortcut_custom.gif
Open in desktop https://desktop.github.com
Reload?https://github.com/commitizen-tools/commitizen/pull/1975/files
docs/images/cli_interactive/shortcut_default.gifhttps://github.com/commitizen-tools/commitizen/pull/1975/files#diff-0205dc25993d8a43aa234cfba3dd2a54ea1e3d336274e77584d09175219e1a12
View file https://github.com/commitizen-tools/commitizen/blob/4606a5198d9432f6be5b37a34a04727df70d4f23/docs/images/cli_interactive/shortcut_default.gif
Open in desktop https://desktop.github.com
Reload?https://github.com/commitizen-tools/commitizen/pull/1975/files
tests/test_out.pyhttps://github.com/commitizen-tools/commitizen/pull/1975/files#diff-6c1d56366ace4ba541ea26d25affc315d272fe8cf0872a891b4c411a99ffc6f3
View file https://github.com/commitizen-tools/commitizen/blob/4606a5198d9432f6be5b37a34a04727df70d4f23/tests/test_out.py
Open in desktop https://desktop.github.com
https://github.co/hiddenchars
https://github.com/commitizen-tools/commitizen/pull/1975/{{ revealButtonHref }}
Please reload this pagehttps://github.com/commitizen-tools/commitizen/pull/1975/files
https://github.com
Termshttps://docs.github.com/site-policy/github-terms/github-terms-of-service
Privacyhttps://docs.github.com/site-policy/privacy-policies/github-privacy-statement
Securityhttps://github.com/security
Statushttps://www.githubstatus.com/
Communityhttps://github.community/
Docshttps://docs.github.com/
Contacthttps://support.github.com?tags=dotcom-footer

Viewport: width=device-width


URLs of crawlers that visited me.