fix(portability): aipass init UTF-8 stdout on Windows + CI e2e lane (DPLAN-0194)

The real T1 Windows bug (diagnosed via the now-reverted error-surfacing
probe): aipass init scaffolds fine, then crashes printing its success
banner — Rich writes the success glyphs through a cp1252 stdout
(UnicodeEncodeError 'charmap'). Same class as the drone fix. The aipass
entry point now reconfigure()s stdout/stderr to UTF-8 in place on Windows.

Also: ci.yml's broad 'pytest --rootdir=.' swept in tests/e2e (which build
a wheel via the dedicated e2e-wheel.yml), failing the unit lane since the
harness landed; now --ignore=tests/e2e in both pytest jobs.

route_command error-surfacing probe reverted to honor 'no function change'
(the masked-error mislabel is noted as a separate @aipass recommendation).

Local: e2e 14/14 green, aipass units 24/24, ruff clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
AIOSAI
2026-06-04 01:17:56 -07:00
co-authored by Claude Opus 4.8
parent 89fa2c1db2
commit 668841417f
3 changed files with 35 additions and 18 deletions
+4 -2
View File
@@ -38,7 +38,9 @@ jobs:
- run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"
- run: coverage run -m pytest -v --tb=short --rootdir=.
# tests/e2e build a wheel + clean venv per the dedicated e2e-wheel.yml
# workflow — they are not part of the fast unit lane.
- run: coverage run -m pytest -v --tb=short --rootdir=. --ignore=tests/e2e
standards:
name: seedgo-audit
@@ -66,7 +68,7 @@ jobs:
- run: |
python -m pip install --upgrade pip
pip install -e ".[dev]"
- run: coverage run -m pytest --rootdir=.
- run: coverage run -m pytest --rootdir=. --ignore=tests/e2e
- run: coverage xml
- uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
with:
+17 -10
View File
@@ -70,18 +70,25 @@ and this project uses [Calendar Versioning](https://calver.org/) in the format
Windows (the old Windows CI ran an editable install, `aipass`-less, and only
routed to in-process modules, so both paths had zero Windows coverage). Pure
portability fixes — Linux/macOS behaviour is unchanged.
- **`aipass init` failed on Windows with a misleading "Unknown command: init".**
`_preflight_check` walks every ancestor of CWD up to the filesystem root
calling `iterdir()`/`is_file()`; at the Windows drive root a locked system
entry (e.g. `pagefile.sys`) raises `OSError`, which the CLI's `route_command`
swallowed and mislabeled. The ancestor walk now skips un-enumerable /
un-stattable entries (logged), so init proceeds normally.
- **`drone @branch` crashed on Windows with `UnicodeEncodeError ('charmap')`.**
`drone` resolved + subprocessed the branch correctly, then crashed *printing*
the captured output: Rich wrote Unicode through a cp1252 stdout. The existing
- **`aipass init` crashed on Windows (surfaced as a misleading "Unknown
command: init").** Init scaffolded the project correctly, then crashed
*printing its `✓ Project initialized` banner* — Rich wrote the ✓/box glyphs
through a cp1252 stdout, raising `UnicodeEncodeError ('charmap')`; the error
handler's `✗` message hit the same wall, bubbling up to the command router
which mislabeled it. The `aipass` entry point now reconfigures stdout/stderr
to UTF-8 in place on Windows. (The init preflight ancestor-walk was also
hardened to skip un-enumerable Windows drive-root entries — defensive, not
the trigger.)
- **`drone @branch` crashed on Windows with the same `UnicodeEncodeError
('charmap')`.** `drone` resolved + subprocessed the branch correctly, then
crashed *printing* the captured output through cp1252 stdout. The existing
`PYTHONUTF8` guard only affected child interpreters, not the live process
streams — `drone`'s entry point now also `reconfigure()`s stdout/stderr to
UTF-8 in place. (DPLAN-0194)
UTF-8 in place.
- **CI unit lane no longer runs the e2e wheel tests.** `ci.yml`'s
`pytest --rootdir=.` swept in `tests/e2e/` (which build a wheel per the
dedicated `e2e-wheel.yml`), failing the unit lane; it now `--ignore`s them.
(DPLAN-0194)
- **A release merge can no longer destroy the `dev` branch** — `drone @git merge`
passed `--delete-branch` to `gh pr merge` unconditionally, so merging a
`dev`→`main` PR deleted the persistent `dev` branch on the remote and stranded
+14 -6
View File
@@ -15,12 +15,25 @@ Auto-discovery architecture:
- No manual imports or routing needed
"""
import os
import sys
import importlib
import traceback
from pathlib import Path
from typing import List, Any
# Windows terminals/pipes default to cp1252, which can't encode the Unicode
# Rich emits (✓/✗, box-drawing, arrows). PYTHONUTF8 only affects child
# interpreters, not this process's already-open stdout/stderr — so we also
# reconfigure the live streams to UTF-8 in place (Python 3.7+). Without this,
# `aipass init` scaffolds correctly but crashes printing its success banner
# with UnicodeEncodeError ('charmap') on Windows. Mirrors drone/cli.py.
if sys.platform == "win32":
os.environ.setdefault("PYTHONUTF8", "1") # for child subprocesses
for _stream in (sys.stdout, sys.stderr):
_reconfigure = getattr(_stream, "reconfigure", None)
if _reconfigure is not None:
_reconfigure(encoding="utf-8", errors="replace")
from aipass.prax import logger
# =============================================================================
@@ -60,12 +73,7 @@ def route_command(command: str, args: List[str], modules: List[Any]) -> bool:
if module.handle_command(command, args):
return True
except Exception as e:
# A module that owns this command but fails must not be masked as
# "Unknown command" — surface the real error (and traceback) to
# stderr so failures are diagnosable instead of silently swallowed.
logger.error(f"[AIPASS] Module {module.__name__} error: {e}")
print(f"[AIPASS] {module.__name__.split('.')[-1]} failed: {e}", file=sys.stderr)
traceback.print_exc(file=sys.stderr)
return False