fix windows unicode separator crash in search output - #205
Conversation
PR Review: fix windows unicode separator crash in search outputExecutive Summary
Affected Areas: Business Impact: Fixes a crash on Windows terminals using cp1252 encoding when running Flow Changes: None — the fix only changes the visual separator character for non-UTF-8 terminals. All data flows, return values, and API contracts are unchanged. Ratings
PR Health
High Priority Issues🔗 #1: Same
|
| File | Line | Code |
|---|---|---|
mempalace/miner.py |
281 | print(f"{'─' * 55}\n") |
mempalace/convo_miner.py |
241 | print(f"{'─' * 55}\n") |
mempalace/split_mega_files.py |
270, 285 | print(f"{'─' * 60}\n") |
mempalace/room_detector_local.py |
202 | print(f"\n{'─' * 55}") |
mempalace/onboarding.py |
59 | print(f"\n{'─' * 58}") |
Suggested fix: Move _separator_line() to a shared module (e.g. constants.py or a new output.py) and reuse it across all modules. This also allows consistent separator widths.
# In mempalace/constants.py (or similar shared location)
import sys
def safe_separator(width: int = 56) -> str:
encoding = getattr(sys.stdout, "encoding", None) or "utf-8"
try:
"─".encode(encoding)
return "─" * width
except (UnicodeEncodeError, LookupError):
return "-" * widthMedium Priority Issues
🎨 #2: Test placed in wrong class
Location: tests/test_searcher.py:46 | Confidence: ✅ HIGH
The new test test_cli_search_uses_ascii_separator_on_cp1252_stdout tests the CLI search() function, but is placed inside TestSearchMemories. The file's own docstring says "Tests the library-facing search interface (not the CLI print variant)."
- class TestSearchMemories:
- ...
- def test_cli_search_uses_ascii_separator_on_cp1252_stdout(self, monkeypatch):
+ class TestSearchCli:
+ def test_uses_ascii_separator_on_cp1252_stdout(self, monkeypatch):🎨 #3: Overly broad exception catch
Location: mempalace/searcher.py:30 | Confidence:
except Exception catches everything including KeyboardInterrupt subclasses on some runtimes. Since this is encoding validation, the only expected failures are UnicodeEncodeError (char not representable) and LookupError (unknown encoding name). Being explicit improves readability and prevents accidentally masking unrelated errors.
- except Exception:
+ except (UnicodeEncodeError, LookupError):Low Priority Issues
🎨 #4: No UTF-8 happy-path test
Location: tests/test_searcher.py | Confidence:
The test only verifies the cp1252 fallback path. A complementary assertion that UTF-8 terminals still produce the ─ separator would prevent regressions where the function always falls back to ASCII.
def test_cli_search_uses_unicode_separator_on_utf8_stdout(self, monkeypatch):
# ... same fake client setup ...
buf = io.BytesIO()
fake_stdout = io.TextIOWrapper(buf, encoding="utf-8")
monkeypatch.setattr("sys.stdout", fake_stdout)
search("anything", "/tmp/fake-palace")
fake_stdout.flush()
output = buf.getvalue().decode("utf-8")
assert " " + ("─" * 56) in outputWhat's Good
- Correct root-cause fix: The encoding probe (
"─".encode(encoding)) is the right approach — it checks capability at runtime rather than guessing by platform. - Efficient pattern:
separator = _separator_line()is computed once before the loop rather than per-iteration. - Good test isolation: The test correctly monkeypatches both
sys.stdoutand the ChromaDB client, avoiding any need for a real palace. - Clean diff: Minimal, surgical change to just the affected code.
Created by Octocode MCP https://octocode.ai 🔍🐙
|
Thanks for the thorough review! All four issues have been addressed:
|
web3guru888
left a comment
There was a problem hiding this comment.
Clean, well-scoped fix for a real Windows UX bug. Here's my full review:
Summary
This PR introduces mempalace/output.py — a new safe_separator() helper that probes sys.stdout.encoding at call time and returns either Unicode box-drawing characters (─) or plain hyphens (-) depending on what the active console can encode. Six modules (searcher.py, miner.py, onboarding.py, entity_detector.py, room_detector_local.py, split_mega_files.py) are updated to use the helper, replacing inline '─' * N expressions. Test coverage includes test_output.py (6 unit tests) and extended test_searcher.py (2 integration tests with fake chromadb client).
What's done well
The safe_separator() design is correct. Probing via "─".encode(encoding) is the right approach — it catches both "encoding doesn't support the codepoint" and "encoding name is unknown" (LookupError). The None fallback to "utf-8" is appropriate for environments where sys.stdout.encoding is unset (e.g., piped output, CI). The getattr guard for missing .encoding attribute handles redirected stdout cleanly.
The separator variable in searcher.py is computed once before the loop rather than once per result — that's a small but correct optimization. If the palette is 50 results, you don't re-probe encoding 50 times.
Test coverage is thorough and uses monkeypatch correctly. The _FakeCollection / _FakeClient mock pair in test_searcher.py is minimal and doesn't leak into other test classes.
Issues and gaps
-
searcher.pystill has a'=' * 60hardcoded Unicode-safe separator. Line 75 in the patch showsprint(f"{'=' * 60}\n")— the equals sign is ASCII and won't crash, but it's inconsistent with the stated goal of "stdout-encoding-aware separators." Not a crash risk, just an inconsistency to note. -
errors="strict"interaction with PR #400. PR #400 (merged or pending) setssys.stdin.reconfigure(encoding="utf-8", errors="strict")on Windows. Ifsys.stdouthas already been reconfigured to UTF-8 strict by PR #400,safe_separator()will always return─on those Windows systems — which is actually correct post-reconfigure. But the two PRs aren't coordinated:safe_separator()checkssys.stdout.encodingwhich may beutf-8after PR #400's reconfigure runs, making the cp1252 fallback path dead code on patched systems. This isn't a bug — it's belt-and-suspenders — but worth a comment inoutput.pyexplaining the interaction. -
split_mega_files.pyhardcoded env var path. The patch touchessplit_mega_files.pywhere there's an unrelatedLUMI_DIR = Path(os.environ.get("MEMPALACE_SOURCE_DIR", str(HOME / "Desktop/transcripts"))). This is pre-existing but theDesktop/transcriptsdefault path looks like a development artifact — someone's local transcript directory baked into a shipping module. Worth flagging to maintainers even if out of scope for this PR. -
encodingdetection happens at print time, not at import time. This is intentional and correct for the MCP server case (stdout may be reconfigured after import). But it means the separator character can change mid-session if something reconfigures stdout between the first and last result print. Unlikely in practice, but worth noting in the docstring. -
No coverage for
split_mega_files.pychanges. The newtest_output.pytests coversafe_separator()in isolation, but thesplit_mega_files.pyintegration isn't directly tested. Minor gap given the function is covered unit-level.
Minor nits
- Docstring in
output.pymentions onlycp1252but the issue applies to any encoding that lacks the U+2500 block (cp1251, cp950, etc.). Consider broadening the note to "legacy Windows codepages" for accuracy. - The PR title is lowercase where the rest of the repo uses sentence case. Cosmetic.
Overall verdict
✅ Approve. The core fix is correct and well-tested. safe_separator() is the right abstraction — centralizing the probe means if future code adds more box-drawing characters, there's a clear place to extend. Address the '=' * 60 inconsistency and the split_mega_files.py stray default path in follow-up if needed. The errors="strict" interaction with PR #400 is worth a comment but not a blocker.
Reviewed by MemPalace-AGI — autonomous research system with perfect memory
b781c01 to
769798a
Compare
|
Hi, thanks for the contribution. This PR has merge conflicts with Could you rebase onto If this change is no longer relevant, feel free to close the PR. (This message is part of a periodic backlog pass, sent to all open PRs that match this state.) |
- extract safe_separator() to shared mempalace/output.py - replace raw ─ prints in miner, split_mega_files, room_detector_local, onboarding - narrow except Exception → except (UnicodeEncodeError, LookupError) in safe_separator - move CLI separator test to TestSearchCli class - add UTF-8 happy-path test to prevent regression
- remove _separator_line wrapper in searcher.py, call safe_separator directly - fix absolute imports to relative (from .output) across all 6 modules - fix entity_detector.py:746 remaining raw U+2500 print (incomplete fix) - add tests/test_output.py with 6 focused unit tests for safe_separator - add try/finally flush in separator encoding tests for robustness
769798a to
19d4d22
Compare
|
Done — rebased onto |
Added stdout-encoding-aware separator fallback in searcher.py so cp1252 consoles use ASCII - instead of Unicode ─.
Added regression test in test_searcher.py that simulates cp1252 stdout and verifies no crash.
Validation: