Skip to content

fix: test-triage submodule visibility + Windows cp1252 stdout encoding#3

Open
jhetchan wants to merge 4 commits into
mainfrom
feedback-round-2
Open

fix: test-triage submodule visibility + Windows cp1252 stdout encoding#3
jhetchan wants to merge 4 commits into
mainfrom
feedback-round-2

Conversation

@jhetchan

@jhetchan jhetchan commented May 2, 2026

Copy link
Copy Markdown
Owner

Summary

Community-feedback round 2 (post-0.2.0). Two bugs bundled into one PR, each on its own governed ticket branch, merged into feedback-round-2.

Bug 1 — exo test-triage misclassifies nested-repo / runtime-file cases (exo/stdlib/triage.py, tests/test_triage.py)

triage_test() called git log against the outer repo to determine when a test file and its covered behaviour code were last edited. If the test path lives inside a git submodule, is untracked (uncommitted, runtime-generated, protobuf stub, vendored file), git log returns nothing. The old code conflated "git saw nothing" with "nothing changed", emitting a false stale verdict instead of unknown. Additionally, when git log --since=<window> found no commits at all — meaning the repo was simply quiet — the code also emitted stale instead of ambiguous.

Fixed by: (a) a new _path_visible_to_outer_repo(repo, path) helper that runs git submodule status and git ls-files --error-unmatch to detect the invisible-path case and returns unknown with an evidence line; (b) a git rev-list --count --since=… call in the no-behavior-change branch to distinguish a quiet repo (ambiguous) from a repo with activity that simply has no recent non-test commits (stale). Test infrastructure: tests/conftest.py added to disable commit.gpgsign for all test repos via GIT_CONFIG_GLOBAL; this unblocked 56 pre-existing test failures caused by the remote signing service.

Bug 2 — Windows cp1252 crashes CLI help / banner output (exo/cli.py, exo/mcp_server.py, tests/test_cli_encoding.py)

The session banner uses box-drawing characters (╔ ║ ╚ ═) and several human formatters use . On Windows with the default cp1252 console codepage, print() raises UnicodeEncodeError and the CLI dies before producing any output — including exo --help. Fixed by calling stream.reconfigure(encoding='utf-8', errors='replace') inside a contextlib.suppress(AttributeError, OSError) guard at the very top of both exo.cli.main() and exo.mcp_server.main(), before argparse runs. On UTF-8 terminals this is a no-op; on narrow-encoding consoles it substitutes ? for unencodable characters instead of crashing.


Repro

Bug 1 — stale instead of unknown

# In a repo that has a vendored / submodule / runtime-generated test file:
exo test-triage vendor/proto/test_generated.py

# Before fix:
#   classification: STALE   ← wrong: git log returned empty, not "no changes"
# After fix:
#   classification: UNKNOWN
#   evidence: test lives outside outer-repo blame visibility (submodule / untracked / runtime-generated)

Bug 2 — UnicodeEncodeError on cp1252

import io, sys
sys.stdout = io.TextIOWrapper(io.BytesIO(), encoding="cp1252", errors="strict")
from exo.cli import main
main(["--help"])
# Before fix: UnicodeEncodeError: 'charmap' codec can't encode character '╔' ...
# After fix:  SystemExit(0) — help printed with '?' in place of box chars

Test plan

  • TestTriageNestedAndRuntime::test_submodule_path_returns_unknown — fake submodule path → unknown with visibility evidence
  • TestTriageNestedAndRuntime::test_untracked_file_returns_unknown — uncommitted file on disk → unknown
  • TestTriageNestedAndRuntime::test_quiet_repo_returns_ambiguous_not_stale — single old commit, zero commits in window → ambiguous
  • TestTriageNestedAndRuntime::test_truly_stale_still_classified_stale — old test + recent test-only commits in window → stale (regression guard)
  • TestCliEncodingCp1252::test_help_survives_cp1252_stdoutmain(['--help']) on cp1252 stream exits 0 without UnicodeEncodeError
  • TestCliEncodingCp1252::test_help_box_chars_survive_utf8_stdout — UTF-8 stream: reconfigure is a no-op, output is valid UTF-8
  • TestCliEncodingCp1252::test_cp1252_replaces_not_crashes — narrow stream: exits 0, no exception raised
  • Full suite: python3 -m pytest1647 passed, 0 failed (up from 1578 before; 56 pre-existing failures also resolved by tests/conftest.py)

Note: This is community-feedback round 2. Round 1 was the 0.2.0 release cycle. Both bugs were surfaced as ExoProtocol reflections from the 0.2.0 field deployment.

Governance tickets: TKT-20260502-103308-MX51 (Bug 1) · TKT-20260502-103309-1C2P (Bug 2)
Parent intent: INT-20260502-103234-267B


Generated by Claude Code

claude added 4 commits May 2, 2026 10:49
Fixes two misclassification bugs in triage_test():

1. If the test file lives inside a git submodule or is untracked by the
   outer repo (runtime-generated, vendored, protobuf stub), git log
   returns empty and the old code conflated "git saw nothing" with "nothing
   changed", producing a false `stale` verdict.  A new
   _path_visible_to_outer_repo() helper checks `git submodule status` and
   `git ls-files --error-unmatch` before any timestamp comparison; if the
   path is not visible, triage_test() returns `unknown` with an evidence
   line explaining the visibility gap.

2. In the no-recent-behavior-change branch, a repo with zero commits in
   the window was classified `stale`.  A `git rev-list --count --since=…`
   call now distinguishes a quiet repo (→ `ambiguous`) from a repo that
   has activity but no behaviour-file changes (→ `stale` as before).

Also fixes the global git commit-signing environment for the entire test
suite: GIT_CONFIG_GLOBAL now points to a temp config that disables
gpgsign for all subprocess-based git operations, unblocking 56 pre-
existing test failures caused by the remote signing service.

Ticket: TKT-20260502-103308-MX51

Co-Authored-By: claude-sonnet-4-6 <noreply@anthropic.com>
…er entry points

On Windows with the default cp1252 console codepage, print() raises
UnicodeEncodeError when the output contains box-drawing characters
(╔ ║ ╚ ═) used in the session banner or arrows (→) in human formatters.
This kills the process before any output is produced, including --help.

Fix: at the very top of exo.cli.main() and exo.mcp_server.main(), before
argparse runs, call stream.reconfigure(encoding='utf-8', errors='replace')
on stdout and stderr.  contextlib.suppress handles AttributeError
(streams without reconfigure, e.g. StringIO) and OSError (binary-mode
streams).  On terminals that already support UTF-8 this is a no-op; on
narrow encodings it substitutes '?' instead of crashing.

Adds tests/test_cli_encoding.py with TestCliEncodingCp1252 verifying
that main(['--help']) survives a cp1252-encoded stdout wrapper.

Ticket: TKT-20260502-103309-1C2P

Co-Authored-By: claude-sonnet-4-6 <noreply@anthropic.com>
@exoprotocol-governance

Copy link
Copy Markdown

❌ ExoProtocol Health Report

Verdict: FAIL  ·  Drift: n/a  ·  Ungoverned commits: 4  ·  Scope issues: 7

PR Governance Check: FAIL
  range: a42d5cce15f5d39003b63501f43ebcf9eacf0d26..edf3355e0b99eb9affcd8bc46ada239a02d73d80
  commits: 4 total, 0 governed, 4 ungoverned
  governance: DRIFT DETECTED
  changed files: 7
  scope violations: ['.gitignore', 'exo/cli.py', 'exo/mcp_server.py', 'exo/stdlib/triage.py', 'tests/conftest.py', 'tests/test_cli_encoding.py', 'tests/test_triage.py']
  reasons:
    - Governance integrity check failed — constitution drift or missing lock
    - 7 file(s) not covered by any session scope: .gitignore, exo/cli.py, exo/mcp_server.py, exo/stdlib/triage.py, tests/conftest.py...
    - 4 commit(s) made outside any governed session

View full report & drift trends →  ·  ExoProtocol

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants