Skip to content

Strict known-bug tests, issue 2 byte oracle, issue 4 fastapi<0.142 pin, pre-commit hooks in CI - #3

Merged
adewale merged 9 commits into
mainfrom
claude/verification-audit-fixes
Oct 11, 2026
Merged

adewale merged 9 commits into
mainfrom
claude/verification-audit-fixes

Conversation

@adewale

@adewale adewale commented Sep 27, 2026 •

Copy link
Copy Markdown
Owner

This PR now carries two earlier PRs, folded together at the owner's request. #4 is merged in with a merge commit, so its commits keep their original SHAs in this history.

From #3 (this PR's original change): strict known-bug semantics, uniform startup, lint in CI

  • Strict known-bug semantics.
    • Resolved issue 2 is now a plain regression assertion.
    • Active issues 3, 4a and 4b use @pytest.mark.xfail(strict=True, raises=PlatformBugReproduced). If an upstream fix lands, the run fails with XPASS(strict) and prints NOT REPRODUCED ... move it to Resolved Issues.
  • tests/conftest.py.
    • Startup budgets stay at main's values: 300 s for 2- under CI, 30 s otherwise. PYWRANGLER_DEV_TIMEOUT overrides them when set by hand.
    • Server output is drained on a background thread.
    • An early pywrangler exit fails immediately.
    • Teardown kills the whole process group, so wrangler/workerd processes no longer leak.
  • CI.
    • The install step warms every repro directory.
    • pre-commit run --all-files runs as a step in main's existing test job, not as a new lint job. It uses SKIP=ruff, because main already runs ruff check.
    • Ruff is main's 0.16.0 (Upgrade Ruff to 0.16.0 and make Workers CI reliable #2), which supersedes this PR's 0.15.2 pin.
  • ruff format cleanup of files that already failed the hook on main.
  • README "Run the tests" section, and LESSONS_LEARNED entries 9 and 10 (renumbered after main's new entries 7 and 8).

From #4: unbreak main, byte-level issue 2 oracle, wire-level issue 5 oracle

  • Product fix. Pins fastapi<0.142 in 4-. 2- no longer needs the pin, because main (Upgrade Ruff to 0.16.0 and make Workers CI reliable #2) now imports its FastAPI app inside the request handler.
    • FastAPI 0.142 imports opentelemetry-api, which calls uuid4() at import time. That raises Errno 29 Randomness is not allowed during Worker startup.
    • pywrangler vendors from pyproject.toml, not the lockfile, so the new release broke main without any commit.
  • test_2. /seed now writes 32768 big-endian words, where word i holds i. /read is checked word by word, so a reordered, repeated or zeroed chunk is caught.
  • test_5. Superseded by main (Upgrade Ruff to 0.16.0 and make Workers CI reliable #2). Its ECHO_URL Worker variable and header_echo_server fixture assert on what the local echo server received. This PR's ?echo= override, its loopback validation and bypass check, tests/echo_server.py and the live httpbin.org call were dropped in the merge.

Deferred patch applied

Follow-ups still open

Local checks on the combined tree (7a68976)

These results predate the merge of main and the pruning. The current checks are under Cost and limits.

Commands are taken from .github/workflows/ci.yml and run with CI=true. Results are decided by exit status.

Command Result
uv sync --dev && for dir in [0-9]*-*/; do (cd "$dir" && uv run pywrangler dev --help); done (under set -e) exit 0
uv run pre-commit run --all-files --show-diff-on-failure (lint job) exit 0, all hooks passed
uv run pytest -vv -rxXs (test job) exit 0: test_2 PASSED, test_5 PASSED, test_3 XFAIL, 4a/4b/4c SKIPPED (deploy-only). 2 passed, 3 skipped, 1 xfailed in 36 s
git merge-tree --write-tree HEAD origin/main clean; origin/main is already an ancestor, so no main merge was needed
  • The merge of origin/claude/test-audit-campaign was clean: tests/test_examples.py auto-merged.
  • test_5 contacts httpbin.org, a public echo service, as the test itself does.
  • Skipped:
    • semgrep.yml, which needs SEMGREP_APP_TOKEN;
    • deployed tests 4a, 4b and 4c, which need --deploy;
    • GitHub Actions itself, which was not run.

Needs the owner

  • Deployed tests 4a, 4b and 4c. Run --deploy. 4a and 4b now carry strict markers, so their outcome is unverified, and a 4b XPASS(strict) is worth investigating. The fastapi<0.142 pin's effect on a deployed issue 4 Worker is also unverified.
  • PR Upgrade Ruff to 0.16.0 and make Workers CI reliable #2. Already merged into main. main is now merged into this branch (merge commit 24bf211), so there is no merge order left to decide.
  • Scheduled CI run. Not added: the owner's verification-cost standard allows no new schedules.

Cost and limits

Pruned to the owner's verification-cost standard on 2026-10-10.

main merged in first. Merge commit 24bf211 brings in #2 (Ruff 0.16.0, reliable Workers CI) and #5 (CI lessons). Where main already did what this PR did, main's version won:

  • Ruff 0.16.0 replaces this PR's 0.15.2 pin.
  • In 2-, main imports the FastAPI app inside the request handler, so 2- no longer needs the fastapi<0.142 pin.
  • For issues 3 and 5, main has the ECHO_URL Worker variable and the header_echo_server fixture. They replace this PR's ?echo= override, its loopback validation and bypass check, tests/echo_server.py, and the live httpbin.org call.
  • Dev-server teardown is main's process-group stop. No wrangler/workerd process from this repo was left after a local run.

Lockfiles were regenerated with uv lock (uv 0.12.3), not hand-merged.

What CI runs for this PR. It is still one workflow (ci.yml) with one job (test). There is no new workflow, job, matrix entry or schedule.

  • Install step. It warms every repro directory with uv run pywrangler dev --help, not only 2-. The first local warm took 30, 18, 16 and 14 s for 2-, 3-, 4- and 5-. For 3- and 5-, that time would otherwise be spent inside their timed dev-server start, so the net addition is mostly the 16 s for 4-. No CI test starts 4-; warming it only checks that its dependencies install and vendor.
  • New step after main's uv run ruff check .. It runs SKIP=ruff uv run pre-commit run --all-files --show-diff-on-failure: ruff format, codespell and file hygiene. It replaces this PR's separate lint job, so ruff check runs once. It took 17-19 s locally with an empty hook cache.
  • Tests. Same uv run pytest -vv and the same three local tests. Locally: 41 s, with 2 passed, 3 skipped and 1 xfailed. Each dev server was ready in 10-13 s.
  • Added per run: roughly 35-60 s. These are local timings; GitHub runner time was not measured.
  • Timeouts and retries. None were raised. The dev-server startup budgets are main's: 300 s for 2- under CI, 30 s otherwise. This PR had raised every directory to 300 s under CI, and that was reverted. PYWRANGLER_DEV_TIMEOUT is a manual override that CI never sets. There are no retries.

Removed from CI

  • The lint job, folded into the test job as described above.
  • The live httpbin.org call in test_5. main took httpbin out of CI on purpose.

Manual only (exact commands)

  • Live HTTPS from requests/urllib3 to a real host (issue 5): cd 5-sync-http-libraries && uv run pywrangler dev, then curl http://localhost:8787/test. Without ECHO_URL, the Worker calls httpbin.org.

  • Deployed tests 4a, 4b and 4c (unchanged, always manual): uv run pytest -vv -rxXs -k test_4 --deploy or --deployed-url <url>. This deploys to Cloudflare, so it was not run here.

  • Issue 4 Worker startup: cd 4-r2-large-binary-roundtrip && uv run pywrangler dev, then curl http://localhost:8787/. Checked locally:

    • with fastapi<0.142, the Worker vendors FastAPI 0.141.1 and / returns 200;
    • without the pin, it vendors 0.143.0 and fails at startup with Errno 29 Randomness is not allowed.

    CI does not start issue 4.

  • Cold local test run: PYWRANGLER_DEV_TIMEOUT=120 uv run pytest -vv, or warm each directory first with uv run pywrangler dev --help.

PBT, fuzzing and mutation testing. This PR has none, so there are no budgets to set and no mutation lanes to remove.

Checks on 11ba67c. Run with CI=true UV_PYTHON=3.13, uv 0.12.3 and Python 3.13.15. Results are decided by exit status.

CI command Result
uv sync --dev + for dir in [0-9]*-*/; do (cd "$dir" && uv run pywrangler dev --help); done exit 0
uv run pytest -vv exit 0: 2 passed, 3 skipped, 1 xfailed, 41 s
uv run ruff check . exit 0
SKIP=ruff uv run pre-commit run --all-files --show-diff-on-failure exit 0: all hooks passed

Not verified

  • GitHub Actions itself (not run).
  • Deployed tests 4a, 4b and 4c, and the effect of the issue 4 pin on a deployed Worker.
  • semgrep.yml, which needs SEMGREP_APP_TOKEN; it is unchanged.
  • Live HTTPS to a real host for issues 3 and 5. This is manual only, as on main.
  • Timings on a GitHub runner.

Permission denials: none.

🤖 Generated with Claude Code

https://claude.ai/code/session_012E6xQEfTtV2ws3dCSMnoCo


Generated by Claude Code

Replace the imperative pytest.xfail() calls, which reported green whether
or not a platform bug reproduced:

- Resolved issue 2 is now a plain regression test: a truncated
  StreamingResponse fails the run.
- Active issues (3, 4a, 4b) use xfail(strict=True,
  raises=PlatformBugReproduced). The bug reproducing is XFAIL; the bug
  disappearing is XPASS(strict), which fails the run and is summarised as
  NOT REPRODUCED; any other error is an ordinary failure. dev_server no
  longer skips starting the server for xfail-marked tests.

Give every repro directory the same pywrangler dev startup budget
(PYWRANGLER_DEV_TIMEOUT, default 300 s under CI, 30 s otherwise) instead
of special-casing 2-, warm every directory in the CI install step, and
drain server output on a thread so a silent hang still hits the deadline
and an early exit fails immediately.

Run pre-commit (ruff, ruff format, codespell, hygiene hooks) in a new CI
lint job, align ruff to 0.15.2 in pyproject and the pre-commit hook, and
apply the ruff format changes the hooks require on the current tree.

Document the conventions in README and LESSONS_LEARNED.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012eisRQmQvg4TphwztxhQGJ
`uv run pywrangler dev` forks npx -> wrangler -> workerd, but teardown
only terminated `uv`. After every test, wrangler and two workerd
processes kept running (7 processes per directory after
`pytest -k test_5`). Across repeated runs they accumulated and slowed
later cold starts, the flakiness the startup budget change targets.

Start the server with start_new_session=True and stop its process group
(SIGTERM, then SIGKILL after 10 s) on normal teardown, on the startup
timeout, and when pywrangler exits before it is ready.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012eisRQmQvg4TphwztxhQGJ
fastapi 0.142.0 (2026-09-29) depends on opentelemetry-api, whose context
module calls uuid4() at import time. Python Workers forbid randomness
during startup, so `pywrangler dev` fails with
"OSError: [Errno 29] Randomness is not allowed while a Worker is starting".
pywrangler resolves the unpinned "fastapi" requirement to the newest
release (the directory's uv.lock is not used for python_modules), so main
broke without a code change.

Red: on main, CI=1 uv run pytest -vv errors at setup of
test_2_fastapi_r2_streaming ("Server failed to start within 300 seconds"),
and `pywrangler dev` in 4-r2-large-binary-roundtrip vendors fastapi
0.142.2 and dies with the same OSError.
Green: with the pin, fastapi 0.141.1 is vendored, test_2 passes and the
issue 4 Worker serves GET /.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SNmM8CmBcqJZ87FWEH5DiF
…eports

test_2_fastapi_r2_streaming checked only the length of GET /read. The
seed was bytes(range(256)) * 512, which repeats every 256 bytes, so even a
byte check could not see reordered 4 KB R2 chunks. /seed now writes 32768
big-endian words where word i holds i, and the test reports the first
misplaced word (a plain bytes == made pytest diff 128 KB for 7.5 minutes
on failure).

test_5_sync_http_libraries took its expected headers from the Worker's
own "headers_sent" field and its observations from the Worker's report of
what httpbin.org echoed. A Worker that skipped a library or reported
HEADERS without sending them stayed green. The Worker now accepts a
loopback-only ?echo= URL; the test runs tests/echo_server.py, a local
httpbin-style echo server, and asserts on the requests that server
recorded: one per library, each with both headers. It no longer needs
outbound network. Against the same Worker, httpbin.org and the local echo
server produced identical results.

Planted bugs, old tests (main) vs new tests:
  seed zeroed                      survived / killed
  /read chunks reversed            survived / killed
  /read drops last chunk           killed   / killed
  urllib3 skipped, result copied   survived / killed
  requests sends no headers,
    Worker reports HEADERS         survived / killed
  urllib3 drops X-Custom           killed   / killed

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SNmM8CmBcqJZ87FWEH5DiF
Review findings on the previous commit:

- `?echo=http://evil.com\@127.0.0.1/` passed the loopback check, because
  urllib.parse reads the host as 127.0.0.1 while requests/urllib3 send the
  request to evil.com. The Worker now rejects userinfo, backslashes,
  queries and fragments, rebuilds the URL from the parsed host, port and
  path, and answers 400 instead of raising a 500. test_5 asserts the 400
  (red on the previous Worker: 500 == 400 fails).
- Moving test_5 to the local echo server dropped coverage of the default
  path: sync requests/urllib3 to a real HTTPS host. test_5 calls it once
  more and checks both headers; the wire-level assertions stay on the
  local echo server.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SNmM8CmBcqJZ87FWEH5DiF
claude added 2 commits October 5, 2026 21:09
PR #4 left this as a follow-up because PR #3 rewrote the /stream lines.
With both folded together, assert the streamed body against the
word-index seed the same way /read is checked.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012E6xQEfTtV2ws3dCSMnoCo
@adewale adewale changed the title Make known-bug tests loud in both directions and CI startup uniform Unbreak main (pin fastapi<0.142), strict known-bug tests, uniform CI startup and lint, stronger issue 2/5 oracles Oct 5, 2026
…udit-fixes

Conflicts resolved by asking whether main already does what this PR did:

- Ruff pin (pyproject.toml, uv.lock, .pre-commit-config.yaml): main's
  0.16.0 supersedes this PR's 0.15.2. Took main's.
- ci.yml: main already runs `uv run ruff check .` in its test job. The
  PR's separate `lint` job is dropped; its remaining hooks (ruff format,
  codespell, file hygiene) run as one step in main's test job with
  SKIP=ruff, so each check runs exactly once. Main's uv 0.12.3 pin and
  UV_PYTHON stay; the PR's warm-every-directory install loop stays.
- 2-fastapi-r2-streaming: main fixed the FastAPI >=0.142 startup failure
  by importing the app inside the request handler (src/app.py), so the
  PR's fastapi<0.142 pin there is dropped. The PR's word-pattern /seed
  data moves into src/app.py.
- 4-r2-large-binary-roundtrip: main did not change its module-level
  FastAPI import, so the fastapi<0.142 pin is kept on top of main's
  dependency list (webtypy removed, workers-py pinned).
- 5-sync-http-libraries and test_5: main's ECHO_URL Worker variable and
  header_echo_server fixture supersede the PR's request-controlled
  ?echo= override, its loopback validation, tests/echo_server.py and the
  live httpbin.org call (main removed httpbin from CI on purpose). Took
  main's entry.py, README and test_5; deleted tests/echo_server.py.
- test_3: main's echo-server assertions plus the PR's strict xfail
  marker and PlatformBugReproduced.
- tests/conftest.py: main's ECHO_URL wiring and process-group teardown;
  the PR's output-drain thread and early-exit error for startup.
- LESSONS_LEARNED.md: main's entries 7 and 8 kept; the PR's entries are
  renumbered 9 and 10.
- Lockfiles regenerated with `uv lock` (uv 0.12.3), not hand-merged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012eisRQmQvg4TphwztxhQGJ
This PR had raised the CI startup budget for every repro directory from
30 s to 300 s. The owner's verification-cost standard allows no raised
timeouts: a longer budget only makes a hung start burn more CI time.

The PR's real remedy for order-dependent startup stays: CI warms every
directory in its install step. After warming, 2-, 3- and 5- reached
"Ready on" in 10-13 s locally, inside the existing 30 s budget. So the
budgets go back to main's values (300 s for 2-fastapi-r2-streaming under
CI, 30 s otherwise). PYWRANGLER_DEV_TIMEOUT stays as an opt-in override
for cold local runs; CI never sets it.

README "Run the tests" and LESSONS_LEARNED entry 10 now describe this,
and the README lists the lint commands CI runs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012eisRQmQvg4TphwztxhQGJ
@adewale adewale changed the title Unbreak main (pin fastapi<0.142), strict known-bug tests, uniform CI startup and lint, stronger issue 2/5 oracles Strict known-bug tests, issue 2 byte oracle, issue 4 fastapi<0.142 pin, pre-commit hooks in CI Oct 10, 2026
@adewale
adewale merged commit ad3bb87 into main Oct 11, 2026
1 check 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.

2 participants