Skip to content

Commit 502798d

Browse files
wshallwshallwshallwshallclaude
authored
docs(worktrees): the local suite is slow, not hung; and uv venv picks cp314t (#813)
* docs(worktrees): the local suite is slow, not hung; uv venv picks cp314t Two findings that had been reported as one cause. Measured 2026-09-03. NO TEST HANGS. A sweep that exited 0 with a subprocess.communicate traceback and no summary line was the CALLER giving up, not pytest. All three communicate(timeout=) sites in tests/ catch it and call pytest.fail, so an escaped one cannot come from a test. Local collection is 15597 items, and the serial rate measured 269 tests in 250s on a box at 100 percent CPU. Not the 042ef7f xdist controller hang. That one needs -n, shows a node down line at 98-99 percent progress, and dies red at the step cap. THE INSTALL FAILURE IS THE INTERPRETER, not watchfiles. uv venv --python 3.14 resolves to cpython-3.14.6+freethreaded (cp314t), which has no watchfiles wheel; the same command on standard cp314 installs one. new.ps1 runs python -m venv, which is why the documented path works. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * docs(worktrees): record the completed run that settles the hang question The first commit argued from the absence of a mechanism. This one adds the positive evidence: CI's engine-leg selection under -n 8 finished here in 717.92s, "1 failed, 11480 passed, 833 skipped". It passed cleanly through the 96-to-99 percent window where the 042ef7f xdist hang starts, with no node down line. Durations settle hang-versus-slow: slowest setup 48.96s, slowest call 21.70s, against --timeout=120. Slow tests exist; a test near the cap does not. The one failure was contention, not a regression. test_accepts_seam.py gives its sandbox child a 15-second wall budget and the box was saturated; the file re-ran alone to "25 passed in 31.61s". Also records the exit-code trap, which reproduced by accident: the run ended with an echo, so the notification announced exit 0 over a red suite. The serial projection drops to a shape rather than a measurement; its arm ran -v unbuffered and is a lower bound on throughput. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: wshallwshall <mefordev@messagefoundry.org> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
1 parent fb878b1 commit 502798d

1 file changed

Lines changed: 87 additions & 0 deletions

File tree

docs/WORKTREES.md

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1060,3 +1060,90 @@ A fresh worktree has no `.venv` (it's git-ignored, exists only in the checkout i
10601060
checkout's code no matter which worktree you run from — silently testing the wrong code. A
10611061
per-worktree venv keeps each session honest. The cost is disk + ~a minute of install (pip's cache
10621062
makes repeats fast).
1063+
1064+
### Build it with `python -m venv`, because `uv venv` picks the free-threaded interpreter
1065+
1066+
`scripts/worktree/new.ps1` runs `python -m venv`, which takes the PATH interpreter. Measured
1067+
2026-09-03 on the shared Windows box, that is standard CPython 3.14.6, ABI tag `cp314`. `uv venv
1068+
--python 3.14` resolves instead to `cpython-3.14.6+freethreaded`, ABI tag `cp314t`. Several pinned
1069+
wheels do not exist for that tag.
1070+
1071+
Same command, two interpreters, nothing else changed:
1072+
1073+
| Interpreter | `uv pip install --only-binary :all: --no-cache watchfiles==1.2.0` |
1074+
|---|---|
1075+
| `cp314t` free-threaded | `watchfiles==1.2.0 has no usable wheels` |
1076+
| `cp314` standard | `+ watchfiles==1.2.0` |
1077+
1078+
Drop `--only-binary` and the free-threaded venv falls back to the sdist, which needs a Rust
1079+
toolchain to build. That is the install failure sessions have been reporting.
1080+
1081+
**The cause is the interpreter, not the package.** `watchfiles` does ship a cp314 wheel: the
1082+
documented install ran here to exit 0 and used `watchfiles-1.2.0-cp314-cp314-win_amd64.whl`. Two
1083+
sessions reporting opposite results were each right about their own interpreter, which is why the
1084+
question stayed open. Check `.venv/Scripts/python.exe -VV` for the words `free-threading build`
1085+
before you blame a dependency.
1086+
1087+
## The full suite takes hours here, and a wrapper timeout reads as exit 0
1088+
1089+
Measured 2026-09-03, fresh worktree, documented install.
1090+
1091+
Local collection is **15597 items**. That is `tests` plus `packaging/messagefoundry-webconsole/tests`
1092+
with the tooling tier included, and CI never runs that set in one call: `ci.yml` splits it into an
1093+
engine leg and a separate web console job. Its measured leg times, their pool and their provenance
1094+
are recorded above the `Tests (pytest)` step in
1095+
[`.github/workflows/ci.yml`](../.github/workflows/ci.yml). Read them there.
1096+
1097+
**Use workers, and run it in the background.** CI's engine-leg selection, under `-n 8` on this box,
1098+
finished in twelve minutes:
1099+
1100+
```
1101+
1 failed, 11480 passed, 833 skipped, 55 warnings in 717.92s (0:11:57)
1102+
```
1103+
1104+
Serially it is hours. The rate measured 269 tests in 250 seconds over the first few hundred, which
1105+
is a rough figure and a lower bound on throughput: that arm ran `-v` with unbuffered per-line
1106+
writes, and the same box completed 12313 tests in twelve minutes once it had eight workers. Take
1107+
"hours serially, minutes parallel" as the shape and do not quote the projection as a measurement.
1108+
1109+
Contention is part of every number here. The box sat at 100 percent CPU with 2.2 GB of 31.7 GB free,
1110+
all from other sessions. That one failure is the cost: `test_accepts_seam.py` gives its sandbox
1111+
child a 15-second wall budget, which a saturated box misses, and the file re-ran alone to `25 passed
1112+
in 31.61s`. **A red under contention is worth re-running alone before you believe it.**
1113+
1114+
### Nothing is close to hanging, and the durations say so
1115+
1116+
The slowest item in that run was 48.96s of setup, and the slowest call 21.70s, against a `--timeout`
1117+
of 120. Slow tests exist. A test near the per-item cap does not.
1118+
1119+
### A run with no summary line establishes nothing
1120+
1121+
A foreground tool call cannot hold the suite. The Claude Code Bash tool caps a foreground `timeout`
1122+
at ten minutes.
1123+
1124+
When a caller's `subprocess.communicate(timeout=...)` gives up it raises `TimeoutExpired` in the
1125+
**calling** process. The captured output is then that traceback, with no `N passed` or `N failed`
1126+
line, and the caller can still exit 0. Read that shape as the wrapper giving up. It is not pytest
1127+
hanging, and the exit code is answering a different question than the one you asked.
1128+
1129+
Pytest itself never raises `TimeoutExpired`, and no test here lets one escape. All three
1130+
`communicate(timeout=...)` sites in `tests/` catch it and call `pytest.fail`, so a test that tripped
1131+
one would show as a named failure inside a normal summary. The wrapper that produced the reported
1132+
traceback was not identified, so treat the ten-minute cap as the fitting mechanism rather than a
1133+
measured one.
1134+
1135+
**The reporting layer really does lose the exit code, and that reproduced here by accident.** The
1136+
run quoted above ended `... && echo "ENGINE_RC=$?"`. Pytest returned 1 for its one failure, the
1137+
`echo` returned 0, and the background task notification therefore announced `exit code 0` over a red
1138+
suite. A trailing command replaces the exit status you were asking about. Read the summary line, not
1139+
the status.
1140+
1141+
### This is not the xdist controller hang, and merging the two would lose both
1142+
1143+
`042ef7ff5` (PR #737) diagnosed a real hang. Under `-n`, a dead worker's replacement never reports
1144+
ready, so the controller polls a silent queue until the step cap. Its signature is a `[gwN] node
1145+
down: Not properly terminated` line at 98-99 percent progress, and the job dies red at
1146+
`timeout-minutes`.
1147+
1148+
A local run carrying no summary line, no `node down` line and exit 0 shares none of that. It also
1149+
cannot share the mechanism, which needs `-n` to exist at all.

0 commit comments

Comments
 (0)