Skip to content

fix: restore CI to green (pin mcp<2, unmask and fix a racy test) - #64

Draft
HarleyCoops wants to merge 1 commit into
mainfrom
claude/fix-ci-mcp2-pin
Draft

fix: restore CI to green (pin mcp<2, unmask and fix a racy test)#64
HarleyCoops wants to merge 1 commit into
mainfrom
claude/fix-ci-mcp2-pin

Conversation

@HarleyCoops

Copy link
Copy Markdown
Owner

CI has been red on main since 2026-07-28 — run #93 at fcad0674 fails the "Run unit tests" step on both Python versions in about two seconds. Three independent problems were stacked behind that, each hiding the next. All reproduced locally in a clean venv with pip install -e ".[dev]".

1. mcp 2.0.0 removed mcp.server.fastmcp

pyproject.toml declared mcp>=1.2 with no upper bound, so CI resolved mcp 2.0.0:

ERROR collecting tests/test_mcp_server.py
mythos/mcp_server.py:28: from mcp.server.fastmcp import FastMCP
E   ModuleNotFoundError: No module named 'mcp.server.fastmcp'
E   RuntimeError: The MCP server requires the 'mcp' extra: pip install -e '.[mcp]'
!!!! Interrupted: 1 error during collection !!!!

FastMCP is not exposed from mcp.server in 2.x either — the subpackage is gone. The except ImportError guard at mythos/mcp_server.py:28-34 re-raises as RuntimeError at module import time, so this killed pytest collection and took the whole suite down rather than just the MCP test.

The error message sends you toward pip install -e '.[mcp]', which is a red herring: the dev extra already includes mcp. The problem is the version, not a missing extra.

Pinned mcp>=1.2,<2 in both the dev and mcp extras. Migrating mythos/mcp_server.py to the 2.x server API is real work and deliberately not attempted here.

2. A fake fp without close() corrupted unrelated tests

tests/test_backends.py builds an HTTPError with fp=ResponseBody(). HTTPError wraps fp in a finalizer that calls close() during garbage collection, and the fixture had no close(). The resulting AttributeError: 'ResponseBody' object has no attribute 'close' surfaced as a PytestUnraisableExceptionWarning attributed to whatever unrelated test happened to be running when the collector got to it — which is why it presented as a failure in test_sol_staged_pipeline.py.

3. The failure that was hiding underneath is a real race

With the GC noise removed, test_codex_streams_jsonl_events_to_trace_and_sink failed on its own merits:

tests/test_sol_staged_pipeline.py:100: assert "thread.started" in observed["trace_during_wait"]
AssertionError: assert 'thread.started' in ''

CodexCli.run streams stdout into the trace from a daemon thread (sol/client.py:143-145) that it only joins after process.wait() returns (sol/client.py:159-160). Nothing orders that reader thread against the fake wait(). The test read the trace exactly once and passed only when it won the race — which it did in isolation, but not under full-suite load.

Replaced the single read with a bounded poll. That preserves what the assertion is actually for — the trace fills during the run, not after it — without racing the reader.

The production code is correct as written and is unchanged by this PR. run() joins both reader threads before touching the trace, so callers always see a complete file. Only the test was wrong.

Verification

  • Full suite: 106 passed, three consecutive clean runs
  • python -m compileall -q mythos
  • math-to-manim --version, serve-mcp --help
  • /health returns {'status': 'ok', 'version': '1.1.0'}
  • MCP server registers all 7 tools

Scope

Two test files and one dependency constraint. No production code, no behavior change.


Generated by Claude Code

CI has been red on main since 2026-07-28 (run #93, fcad067). Three
independent problems, all reproduced locally in a clean venv.

1. mcp 2.0.0 removed mcp.server.fastmcp. pyproject declared "mcp>=1.2" with
   no upper bound, so CI resolved 2.x and mythos/mcp_server.py failed to
   import FastMCP; FastMCP is not exposed from mcp.server either. The
   except ImportError guard re-raises as RuntimeError at module import time,
   which killed pytest collection and took the entire suite down rather than
   just the MCP test. Pin mcp>=1.2,<2 in both the dev and mcp extras;
   migrating to the 2.x server API is separate work.

2. tests/test_backends.py built an HTTPError with a fake fp lacking close().
   HTTPError wraps fp in a finalizer that calls close() on collection, so the
   AttributeError surfaced as an unraisable exception attributed to whatever
   unrelated test was running at GC time. Give the fixture a close().

3. With that noise removed, test_codex_streams_jsonl_events_to_trace_and_sink
   failed on a real race. CodexCli.run streams stdout to the trace from a
   daemon thread that it only joins after process.wait() returns, so nothing
   orders the reader against the fake wait(). The test read the trace once and
   passed only when it won the race — it did in isolation, not under full-suite
   load. Poll for the streamed event instead, preserving the assertion's intent
   that the trace fills during the run rather than after it.

Production code is unchanged. Full suite: 106 passed, three consecutive clean
runs. compileall, CLI help, /health, and MCP tool registration all verified.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VQpUQYoSDgvMZkVF2NbGiq
@socket-security

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updatedpypi/​mcp@​2.0.0 ⏵ 1.29.099 +1100100100100

View full report

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