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:
co-authored by
Claude Opus 4.8
parent
89fa2c1db2
commit
668841417f
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user