Skip to content

test(env): detect stale editable installs with one clear failure (#1649) - #1650

Merged
axisrow merged 2 commits into
mainfrom
ao/fix-issue-1649/root
Oct 21, 2026
Merged

axisrow merged 2 commits into
mainfrom
ao/fix-issue-1649/root

Conversation

@axisrow

@axisrow axisrow commented Oct 20, 2026

Copy link
Copy Markdown
Owner

Closes #1649

Summary

Investigation of the ~9–13 local-only failures on clean main, then in-repo protection against recurrence.

Investigation verdicts (control run: clean main, both lanes, 10 206 tests, local py3.13.15)

  1. Stale editable metadata — confirmed, deterministic (5 failures, not 4). The installed dist-info (dated 17.10, before the packaging: wheel ships tg_agent package; tg-agent --version prints correct name (#1448, #1447) #1582 src/ → tg_agent/ rename) kept tg-agent = src.main:main in entry_points.txt, top_level.txt = src, and a bin/tg-agent wrapper importing src.main. Every console-entrypoint test died with ModuleNotFoundError: No module named 'src'. Note: pip show tg-agent reported the current version 0.2.4.1 — the version alone does not reveal the staleness.
  2. py3.13 vs py3.11 GC behavior — refuted. The 9 test_run_chat_* PytestUnraisableExceptionWarning failures were a test-helper leak: the old fake_asyncio_run used a bare new_event_loop() without shutdown_asyncgens(), so async generators abandoned by the consumer break were finalized after the loop closed. perf(test): speed up full pytest runs and fix isolation #1647 (merged 20.10 22:53, after the 20.10 worker wave) replaced it with asyncio.Runner, which shuts asyncgens down. The same py3.13 environment now shows zero such failures — Python version is not the driver.
  3. ~60s tg-agent post-process gates — refuted for this symptom set: no timeouts in the control run; all failures were deterministic import errors.

A fresh editable install repairs the environment (verified: pip install -e . --no-deps --no-build-isolation fixed entry_points.txt, top_level.txt, and bin/tg-agent; console tests went from 5 failed to 11 passed).

What lands here (detector + docs, per the issue's scope guard)

  • editable_install_staleness() in tests/helpers.py — compares the installed dist (console-script target, version, top_level.txt) against pyproject.toml. The probe runs in an isolated python -I subprocess on purpose: the pytest process puts rootdir on sys.path, so a stray tg_agent.egg-info build artifact in the repo root shadows the site-packages dist-info and hides the divergence from an in-process check (this bit me during the investigation — the stale state intermittently "disappeared" while a build artifact existed).
  • tests/test_local_install_consistency.py — fails once with the diagnosis and the reinstall recipe in the message (proven red against the genuinely stale env, then green after the reinstall).
  • tests/test_cli_exit_codes.py — console variants skip with the same recipe while the install is stale; module variants still run.
  • CONTRIBUTING.md — "Local venv policy": no local Python-version pin beyond requires-python (CI pins 3.11), the reinstall recipe, and where the editable install lives for AO worktrees.
  • CLAUDE.md — Commands block points at the detector for the ModuleNotFoundError: 'src' diagnosis.

Tests

  • Detector red→green cycle against the real stale then repaired environment (11 passed: detector + all 10 exit-code tests).
  • Full suite, both lanes, local py3.13.15 via the main .venv: parallel lane pytest tests -q -m "not aiosqlite_serial" -n auto, serial lane -m aiosqlite_serial -n auto --dist=loadfile (results in the CI run for this PR).
  • ruff check clean on the touched files; filterwarnings = ["error"] untouched.

Risks / follow-ups

  • The detector's version check will red after release-bump merges until the local editable install is refreshed — intentional (the recipe is one command), CI always installs fresh and is unaffected.
  • The stale src/ directory in the main checkout is untracked leftovers outside this repo's scope; removal is left to the owner.

axisrow and others added 2 commits October 21, 2026 01:54
An editable install that predates the #1582 src/ -> tg_agent/ rename keeps
its dist metadata pointing at src.main:main, so the console-entrypoint tests
in test_cli_exit_codes.py fail on every local run with a cryptic
"ModuleNotFoundError: No module named 'src'" while CI (fresh install each
job) stays green.

- editable_install_staleness() in tests/helpers.py compares the installed
  dist (console script target, version, top_level) against pyproject in an
  isolated `python -I` subprocess: the pytest process puts rootdir on
  sys.path, so a stray tg_agent.egg-info build artifact in the repo root
  shadows the site-packages dist-info and hides the divergence from an
  in-process check.
- tests/test_local_install_consistency.py fails once with the diagnosis and
  the reinstall recipe in the message.
- test_cli_exit_codes.py skips its console variants with the same recipe
  while the install is stale; the module variants still run.

Co-Authored-By: Claude Code <noreply@anthropic.com>
)

- CONTRIBUTING.md: local venv policy — any Python within requires-python
  (3.11+) works, no local version pin (the #1649 "py3.13 failures" were a
  stale install plus a test-helper leak, not a version behavior difference);
  the reinstall recipe; where the editable install lives for AO worktrees.
- CLAUDE.md: point at tests/test_local_install_consistency.py from the
  Commands block so a ModuleNotFoundError: 'src' diagnosis is one lookup.

Co-Authored-By: Claude Code <noreply@anthropic.com>

@axisrow axisrow left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: #1650 — stale editable install detector (#1649)

Approved. No change-requiring findings.

Scope vs origin/main (eab3d69): 5 files, +124/−3 — the detector editable_install_staleness() in tests/helpers.py, the new tests/test_local_install_consistency.py, console-variant skips in tests/test_cli_exit_codes.py, and the CLAUDE.md / CONTRIBUTING.md policy notes. Matches the #1649 scope guard (detector + docs, no production code touched).

Verified:

  • Probe inputs exist: [project.scripts] tg-agent and [project] version in pyproject.toml — no KeyError path; read_text returning None for a missing top_level.txt is handled; an absent dist returns None, so a bare CI job stays green.
  • All three console-variant tests in test_cli_exit_codes.py carry the skip mark via pytest.param(marks=...); module variants still run; param ids unchanged, so both lanes keep their identity.
  • CI on the head commit is green across all 8 checks (tests-shards ×3 and tests-serial included), so the version and top_level checks do not false-positive on a fresh CI editable install; the detector runs in the parallel lane.
  • xdist cost: the lru_cache is per-process, so each worker spawns the isolated -I probe once per run — negligible.
  • The -I subprocess isolation rationale (a repo-root egg-info artifact shadowing the site-packages dist-info) is sound and documented inline; no injection surface (fixed probe string, no user input).

Notes, no change required:

  • subprocess.run(timeout=30) raises TimeoutExpired uncaught — it would surface as a collection error in test_cli_exit_codes.py instead of the friendly diagnosis. Extremely unlikely with plain interpreter startup; fine to leave.
  • Red after release-bump merges until the local editable install is refreshed is the documented, accepted tradeoff (PR body + CONTRIBUTING.md): the failure message carries the one-command repair, and CI always installs fresh.

@axisrow
axisrow merged commit 6b6042f into main Oct 21, 2026
9 checks passed
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.

[P2] локальное editable-окружение: stale entrypoints + py3.13 venv дают ~9-13 ложных фейлов на чистом main

1 participant