Repository navigation
Description
pyproject.toml sets addopts = "--block-network -p no:tach", and on Windows that stops the
suite before a single test runs — not a few failures, the entire run.
Mechanism
pytest-recording implements --block-network by replacing socket.socket.connect. Windows has no
AF_UNIX socketpair(), so socket.socketpair() falls back to _fallback_socketpair, which opens a
listening socket on 127.0.0.1 and connect()s to it. asyncio's ProactorEventLoop builds its
self-pipe with exactly that call, so the loop cannot be constructed while the guard is active:
asyncio/runners.py -> events.new_event_loop()
asyncio/windows_events.py:316 -> super().__init__(proactor)
asyncio/proactor_events.py:781 -> self._make_self_pipe()
asyncio/proactor_events.py:635 -> self._ssock, self._csock = socket.socketpair()
socket.py:629 (in _fallback_socketpair) -> csock.connect((addr, port))
pytest_recording/network.py:124 -> raise RuntimeError("Network is disabled")
So asyncio.run() itself raises, which is why every async test errors during setup rather than
asserting anything. On Linux socketpair() goes through the C-level _socket.socketpair() and never
calls Python connect(), so nothing surfaces there — and since publish.yml only smoke-tests the
built distributions (check_installed_distribution.py), no CI runs pytest on any platform.
Measured on Windows, Python 3.14.7, uv sync --all-extras, no extra flags
uv run pytest -q |
result |
|---|---|
| before | 201 failed, 223 passed, 64 errors |
| after the one-line change below | 8 failed, 416 passed |
The change
allowed_hosts is one of the keys this repo's own vcr_config fixture already owns, and it is matched
by re.match against the connect target — so only loopback comes back, and provider endpoints stay
refused. In tests/conftest.py:
return {
+ # --block-network (see addopts) blocks every socket connect, which on Windows
+ # takes the suite down before a single test runs: there the ProactorEventLoop
+ # builds its self-pipe with socket.socketpair(), whose Windows fallback
+ # connects to 127.0.0.1, so asyncio.run() itself raises "Network is disabled".
+ # Loopback is the only host let back through; provider endpoints stay blocked.
+ "allowed_hosts": ["127.0.0.1"],
# The registered JSON serializer keeps HTTP bodies as nested objects, so the
# cassette diffs like its payloads instead of escaped string literals.
"serializer": "json",It is deliberately not a change to addopts: the flag stays exactly as it is, and no test is touched.
Why allowing loopback does not weaken the guard
Verified directly, with the change in place: a loopback connect succeeds, while
socket.socket().connect(("example.com", 80)) still raises RuntimeError: Network is disabled.
One thing I did not touch
8 tests in tests/test_client_with_live_apis.py still fail after this — all of them the -anthropic
variants, failing inside VCR's httpx stub rather than at loop construction. They are visible once the
loop can be built, they look like a separate cassette-vs-SDK-version mismatch, and I have left them
alone rather than fold an unrelated change into this report.
I have the change on a branch (PerryLink:fix/windows-block-network, the 7 added lines above, one file),
but opening a pull request here is refused for this account — createPullRequest returns
FORBIDDEN and viewerPermission is READ — so an issue is the only channel I have. The branch is
there if you would rather take the diff from it than retype it.
Activity
rmnvg commentedon Sep 22, 2026
Confirming @PerryLink's diagnosis and fix — traced through the same mechanism (Windows
socket.socketpair()fallback callingconnect(), whichProactorEventLoop._make_self_pipe()hits on everyasyncio.run(), tripping pytest-recording's guard before any async test body runs).Applied the exact one-line change (adding
"allowed_hosts": ["127.0.0.1"]to thevcr_configfixture intests/conftest.py) and ran the full suite on macOS:424 passed, no regressions — expected, sincesocketpair()doesn't go throughconnect()outside Windows, so this is a no-op there and only matters on the platform where it's currently broken.Like @PerryLink, I don't have write access to open a PR against this repo (
createPullRequestis forbidden for non-collaborators here). The change is on a branch on my fork if a maintainer wants to pull it directly instead of retyping it:https://github.com/rmnvg/system-one-adapter-python/tree/fix/windows-block-network
Diff: rmnvg@2f98b76
return { + # --block-network (see addopts) blocks every socket connect, which on Windows + # takes the suite down before a single test runs: there the ProactorEventLoop + # builds its self-pipe with socket.socketpair(), whose Windows fallback + # connects to 127.0.0.1, so asyncio.run() itself raises "Network is disabled". + # Loopback is the only host let back through; provider endpoints stay blocked. + "allowed_hosts": ["127.0.0.1"], # The registered JSON serializer keeps HTTP bodies as nested objects, so the # cassette diffs like its payloads instead of escaped string literals. "serializer": "json",aoright commentedon Sep 24, 2026
I verified this fix against latest
main(e1d4cc9, post-v0.2.1) on fork branchfix/windows-block-network(commitfd09780).Compare: main...aoright:system-one-adapter-python:fix/windows-block-network
Summary
allowed_hosts: ["127.0.0.1", "localhost"]:127.0.0.1) and hostname (localhost) ensures WindowsProactorEventLoop._make_self_pipeand any local test harness connects proceed unhindered.--block-network, preserving the offline cassette replay guarantee.v0.2.1suite:uv run pytest: 424 passed.uv run pyrefly check: 0 errors.uv run ruff check tests/conftest.py: clean (0 findings).PerryLink commentedon Sep 25, 2026
Thanks @rmnvg and @aoright — the independent runs are the useful part here. macOS
424 passedis what the mechanism predicts (nothing on the self-pipe path goes throughconnect()off Windows, so the change is a no-op there), and @aoright's run againste1d4cc9shows it still applies after v0.2.1.I re-checked
maintoday:pyproject.tomlstill setsaddopts = "--block-network -p no:tach", andvcr_configintests/conftest.pystill returns noallowed_hosts. So the suite is still unrunnable on Windows exactly as reported — the fix is still live, not stale.On the one difference between the two branches — @aoright added
"localhost"next to"127.0.0.1". Both work, but only the first is needed for this bug._fallback_socketpairbinds("127.0.0.1", 0)and connects to the address it gets back, so the host it connects to is the literal127.0.0.1. pytest-recording matches viare.matchover the entries joined into one alternation (pytest_recording/network.py:164-168), so"localhost"only adds coverage for code that connects by name. Harmless to keep; if a minimal diff is preferred, one entry is enough.Worth knowing for anyone extending this list later: because the entries are joined into a single pattern and matched with
re.matchrather than compared for equality, each entry is a regex, and.matches any character. Fine for127.0.0.1, but a future entry is a pattern, not a literal.Still untouched, as noted in the report: the 8
-anthropiccases intests/test_client_with_live_apis.pyfail inside VCR's httpx stub rather than at loop construction, which is why they survived this change. Leaving them for whoever owns the cassettes.simin-yuan commentedon Nov 2, 2026
Fixed upstream in pytest-recording 0.14.1 via kiwicom/pytest-recording#213.