From d565323aa40772679b35354c8246bfc7d3e7651c Mon Sep 17 00:00:00 2001 From: shieldstarinvestments-lang <267153277+shieldstarinvestments-lang@users.noreply.github.com> Date: Sat, 11 Apr 2026 23:15:42 -0400 Subject: [PATCH] fix(hooks): force UTF-8 stdio so Windows post-commit/post-checkout don't crash On Windows, Python defaults to cp1252 for sys.stdout, sys.stderr, and open() when not attached to a TTY. Git hooks always run detached. Any Unicode character outside Latin-1 that flows through a graphify rebuild (arrows, checkmarks, math symbols like U+2264, box drawing chars, etc.) crashes the encoder with: 'charmap' codec can't encode character '\u2264' in position N: character maps to Reproduction: on Windows, create a graphify graph whose output (e.g. GRAPH_REPORT.md) contains any "<=" or similar symbol. Commit any file. The post-commit hook runs _rebuild_code(Path('.')), which crashes inside a print() or file write. Hook exits 1, graph never rebuilds again until the user manually runs graphify. Fix: in both _HOOK_SCRIPT (post-commit) and _CHECKOUT_SCRIPT (post-checkout), set PYTHONIOENCODING=utf-8 and PYTHONUTF8=1 before invoking the Python interpreter. PYTHONUTF8 is the Python 3.7+ UTF-8 mode which covers both stdio AND open(), so file writes inside graphify.watch become UTF-8 as well. Belt and suspenders: inside the inline Python block, also call sys.stdout/stderr.reconfigure(encoding='utf-8', errors='replace'). This covers the edge case where some exotic shell wrapper strips the env vars before Python starts. The errors='replace' fallback means any truly unmappable char becomes '?' instead of raising, so the rebuild always completes. No behavior change on macOS/Linux (where stdio is already UTF-8 by default and these env vars are no-ops). Verified by running _rebuild_code(Path('.')) directly under PYTHONIOENCODING=utf-8 PYTHONUTF8=1 on Python 3.12 / Windows 10. Rebuild that previously crashed now completes with the expected "Rebuilt: N nodes, M edges, K communities" output, and graph.json + GRAPH_REPORT.md are regenerated cleanly. Co-Authored-By: Claude Opus 4.6 (1M context) --- graphify/hooks.py | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/graphify/hooks.py b/graphify/hooks.py index d99a8c4a7..632e287cc 100644 --- a/graphify/hooks.py +++ b/graphify/hooks.py @@ -40,12 +40,28 @@ exit 0 fi +# Force UTF-8 for Python I/O so Unicode characters in graph output (e.g. the +# arrows, checkmarks, and math symbols that appear in generated reports) do +# not crash the encoder on Windows, where stdio and file I/O default to +# cp1252 when not attached to a TTY. Git hooks always run detached. +export PYTHONIOENCODING=utf-8 +export PYTHONUTF8=1 + """ + _PYTHON_DETECT + """ export GRAPHIFY_CHANGED="$CHANGED" $GRAPHIFY_PYTHON -c " import os, sys from pathlib import Path +# Belt and suspenders: even if the env vars above get dropped by some exotic +# shell wrapper, reconfigure stdio to UTF-8 with a 'replace' fallback so a +# stray non-Latin-1 character never crashes the hook. +try: + sys.stdout.reconfigure(encoding='utf-8', errors='replace') + sys.stderr.reconfigure(encoding='utf-8', errors='replace') +except Exception: + pass + changed_raw = os.environ.get('GRAPHIFY_CHANGED', '') changed = [Path(f.strip()) for f in changed_raw.strip().splitlines() if f.strip()] @@ -84,12 +100,22 @@ exit 0 fi +# Force UTF-8 for Python I/O (see post-commit hook for the same rationale). +export PYTHONIOENCODING=utf-8 +export PYTHONUTF8=1 + """ + _PYTHON_DETECT + """ echo "[graphify] Branch switched - rebuilding knowledge graph (code files)..." $GRAPHIFY_PYTHON -c " +import sys +# Belt and suspenders stdio reconfigure in case env vars get dropped. +try: + sys.stdout.reconfigure(encoding='utf-8', errors='replace') + sys.stderr.reconfigure(encoding='utf-8', errors='replace') +except Exception: + pass from graphify.watch import _rebuild_code from pathlib import Path -import sys try: _rebuild_code(Path('.')) except Exception as exc: