Repository navigation
test(env): detect stale editable installs with one clear failure (#1649) - #1650
Merged
Merged
Conversation
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
commented
Oct 20, 2026
axisrow
left a comment
Owner
Author
There was a problem hiding this comment.
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-agentand[project] versionin pyproject.toml — no KeyError path;read_textreturning 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
-Iprobe once per run — negligible. - The
-Isubprocess 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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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)
src/→tg_agent/rename) kepttg-agent = src.main:maininentry_points.txt,top_level.txt = src, and abin/tg-agentwrapper importingsrc.main. Every console-entrypoint test died withModuleNotFoundError: No module named 'src'. Note:pip show tg-agentreported the current version 0.2.4.1 — the version alone does not reveal the staleness.test_run_chat_*PytestUnraisableExceptionWarningfailures were a test-helper leak: the oldfake_asyncio_runused a barenew_event_loop()withoutshutdown_asyncgens(), so async generators abandoned by the consumerbreakwere 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 withasyncio.Runner, which shuts asyncgens down. The same py3.13 environment now shows zero such failures — Python version is not the driver.A fresh editable install repairs the environment (verified:
pip install -e . --no-deps --no-build-isolationfixedentry_points.txt,top_level.txt, andbin/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()intests/helpers.py— compares the installed dist (console-script target, version,top_level.txt) againstpyproject.toml. The probe runs in an isolatedpython -Isubprocess on purpose: the pytest process puts rootdir onsys.path, so a straytg_agent.egg-infobuild 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 beyondrequires-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 theModuleNotFoundError: 'src'diagnosis.Tests
.venv: parallel lanepytest 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 checkclean on the touched files;filterwarnings = ["error"]untouched.Risks / follow-ups
src/directory in the main checkout is untracked leftovers outside this repo's scope; removal is left to the owner.