Repository navigation
Harden flaky CI tests: recorder teardown race, per-model e2e cases, tracing polling, smaller smart-routing journeys - #1245
Conversation
lilly-luo
left a comment
There was a problem hiding this comment.
The recorder teardown fix is well diagnosed, and test_teardown_leaves_client_open_for_forward_that_outlives_drain covers it properly. The per-model parametrization and the scoped e2e reruns look right.
Blocking: rebase onto main. The branch is 42 commits behind and conflicts. Since it was cut, #1222, #1212 and #1270 changed test_ug_smart_routing_hooks.py:
- They added
MANAGED_DEFAULTcases. - They added a
smart_routing=kwarg touse_smart_routing_fixture. Legacy env-flag cases passsmart_routing=Falsebecause "managed smart routing would override the legacy env vars". - #1270 quarantined the Claude toggle journeys that this PR splits.
The new _select_smart_router_version has neither the MANAGED_DEFAULT cases nor the kwarg, so a plain conflict resolution would silently drop coverage. See the inline comment for a version that keeps both.
CI is also stale. The last run (2026-10-22) is red on e2e, all integration tests, and Codex · Windows full journeys (10 failed). The Windows unit failure (test_catalog_browsing small-terminal render) looks unrelated. These live changes still need a green run after the rebase.
Non-blocking nits are inline.
ae94b3f to
dd012c7
Compare
lilly-luo
left a comment
There was a problem hiding this comment.
Re-review after rebasing onto main (dd012c7e, force-pushed). The branch now merges cleanly. Here is how I resolved the conflicts, so you can check them:
Main's changes carried into the split tests
test_ug_smart_routing_hooks.py:(True, MANAGED_DEFAULT)is inSMART_ROUTER_VERSIONS._select_smart_router_versionskips the selector env var forMANAGED_DEFAULTand passessmart_routing=config_version is not None, as main does (#1212).- The Agent/Task tool-description assertions from #1290 now live in
_assert_claude_calculation_requests, through a neworchestration_enabledkwarg (conditionorchestration_enabled and routed). - The #1270 quarantine (
@pytest.mark.soft_fail) is on all three Claude split tests: 18 cases. The Codex splits stay blocking.
test_cuj4_smart_routing.py: main's route-options checks are in the first-prompt-routed block. Child routes are matched by decisiontask_name(#1258) instead ofchild_task.filename.
Fixes for code that only broke after the rebase
scripts/run_integration.py:WINDOWS_UNSUPPORTED_NODESdeselected the oldtest_smart_router_skill_toggles_codex_subagent_routingby node id. It now lists the three Codex splits; without that, they would have started running on Windows.tests/test_integration_contract.py: the #1270 soft-fail contract checks now use aCLAUDE_TOGGLE_TESTStuple of the split names. The Codex retained test is nowtest_smart_router_skill_turns_codex_subagent_routing_back_on.- READMEs: merged both sides. The integration README soft-fail section now names the three split tests.
Local checks
- ruff check and format are clean. Contract and runner tests: 102 passed. Recorder tests pass.
- Collection: 46 smart-routing integration cases (18
soft_fail), 109 e2e_cuj, and 36 e2e with no workspace set. - The full unit suite has 7 failures (
test_e2e_user_agentandtest_smart_router). The same 7 also fail onorigin/mainon my machine. - No live runs. CI is running now.
Still open from my first review (the code is unchanged):
test_cuj2_mps_mcp.py:227: the waits use separate 240 s timeouts, not one shared budget, and an abandoned later candidate masks the real 502. This is the one I'd fix before merging.- Nits: the recorder's mid-stream 502 comment, the shallow copy in
e2e_state, and the collection-time catalog fetch.
lilly-luo
left a comment
There was a problem hiding this comment.
Second re-review at dd012c7e, this time with an independent read that doesn't rely on my conflict resolution.
CI: 52 checks passed. The only blocking failure is CUJ 4 test_smart_router_preset_routes_first_prompt_then_explicit_native_subagent[subagent_orch_v1-claude] (assert 2 == 1 at test_cuj4_smart_routing.py:445). Claude spawned two subagents for the same delegated prompt, and both hit routes:select with identical prompts (seq 104 and 109). That's the known extra-subagent flake (see #1230 and this PR's "Not covered" section), not the rebase. All integration tests is red only because of it. Codex · Windows full journeys pass, which confirms the updated WINDOWS_UNSUPPORTED_NODES deselect.
Should fix: the split dropped the routed → off → unrouted scenario. On main, each toggle journey routed a child, turned routing off, and then checked that the next child was not routed. In the split, every off toggle happens either before anything was routed (*_off, and Codex _back_on) or right before an on toggle (Claude _back_on). No test now catches a hook or mod that caches routing state after the first routed spawn, so /smart-router off stops working mid-session. See the inline suggestion.
Minor
- The tracing tests now poll
countand thenmodel_count, each for up to about 300 s plus 60 s more for a slow statement. Both queries usetime > current_timestamp() - INTERVAL 10 MINUTES(test_ug_claude_tracing.py:56andtest_ug_codex_tracing.py:56). A slow agent run plus a late export can push the span outside that window, which reports a false "no rows". Consider widening the window or anchoring it to the test's start time. - The earlier comments on CUJ 2
_served_marker_request, the shallow copy, and the recorder's mid-stream 502 still apply. Two more are inline.
Checked with no findings: the CUJ 4 split keeps every assertion and preset, the band and hint-row checks, the env and machine-wide restore, and main's #1258 and #1290 changes. The hooks file keeps #1270 soft_fail on all three Claude tests, and its ids and markers match main. No stale references to the renamed tests remain in scripts, contract tests, workflows or READMEs. The --only-rerun patterns match both the pytest.fail messages and a raw TimeoutExpired.
…outs once The per-model launch tests looped over the live workspace catalog and failed as a whole when any one model misbehaved, so a single bad model turned every PR red. Parametrize them at collection time from a cached catalog fetch (shared with the e2e_state fixture) so each model is its own case. Without UCODE_TEST_WORKSPACE nothing touches the network; a catalog fetch error is a single failing case, not a silent skip. Add pytest-rerunfailures and rerun an e2e shard case once only for live timeouts and transient upstream errors (TimeoutExpired, "timed out after", "TIMEOUT after", TEMPORARILY_UNAVAILABLE, REQUEST_LIMIT_EXCEEDED); reruns are listed in the summary. Integration and CUJ suites keep failing on the first attempt per tests/AGENTS.md. Co-authored-by: Isaac <no-reply@databricks.com>
The tracing tests slept 30s and queried once, and gave each SQL statement
180s, so a cold warehouse or ingestion lag failed them ("SQL statement did
not finish", "assert 0 > 0"). Give a statement 300s and poll the trace-row
count every 15s for up to 300s. The tests still assert the row exists.
Co-authored-by: Isaac <no-reply@databricks.com>
The recorder's handler threads are daemons, and teardown closed the httpx client while a forward could still be streaming. That frees the socket descriptor while OpenSSL keeps reading the old number, so a later connection in the same process (the next CUJ class's SDK calls and inference requests) had bytes stolen: "Response ended prematurely", SSL RECORD_LAYER_FAILURE, "incomplete chunked read" and "Bad file descriptor". Every one of the 22 flaky CUJ 2 Claude failures ran right after a passing Codex class. Teardown now waits for in-flight forwards before closing the client and leaves a forward that outlives the wait to close its own connection. Also: - Record an upstream transport failure as the 502 the agent saw, so waiters fail immediately with the cause instead of timing out after 240s. - Do not reuse pooled upstream connections. - CUJ 2 asserts the first marker-bearing inference request that was served with HTTP 200 (an agent-retried transport failure no longer fails it), and uses one budget for the request and response waits. Co-authored-by: Isaac <no-reply@databricks.com>
…tests Both ran one long live session per config version, so one slow subagent or model call failed every assertion chained after it. - CUJ 4: test_smart_router_config_version becomes a per-agent preset routing test and a Claude status-band/hint/toggle test that needs no delegated work. - Skill toggles: each agent's test becomes at-launch, off and back-on tests, each in a fresh session; back-on keeps the off->on sequence (and Claude's launch-recipe comparison) that it proves. All existing assertions and config versions are kept. Coverage READMEs are updated for the new test names. Co-authored-by: Isaac <no-reply@databricks.com>
…e2e catalog cache on workspace The skill-toggle split dropped main's on -> off -> unrouted step, so no test proved /smart-router off stops routing after a child was already routed. Claude and Codex back-on tests now run routed 1+1, off, unrouted 1+2, on, routed 2+2. e2e_state_for keyed on the token, so with a bearer command that mints a token per call the collection-time fetch and the e2e_state fixture never shared the cache. Key on the workspace only and fetch the token inside. Co-authored-by: Isaac <no-reply@databricks.com>
dd012c7 to
4e50fd6
Compare
…tabricks#1612) ## Summary `TestGeminiAuthRecovery::test_recovers_when_initial_token_empty` flakes when the live Gemini model is slow. Its direct gateway call (`_run_gemini_gateway_smoke`) uses `urlopen(timeout=30)`, and on a slow response that raises a bare `TimeoutError: The read operation timed out`. The e2e shards already rerun live timeouts once (databricks#1245), but only failures matching `timed out after [0-9]+s`, so this case fails on the first attempt while the CLI launch tests next to it get their rerun. This change converts a socket timeout (bare, or wrapped in `URLError` on connect) into `AssertionError: Gemini gateway smoke for model=<m> timed out after 30s`. That matches the existing `--only-rerun` pattern. HTTP errors and other `URLError`s still fail immediately. Tests only; no product code. Seen on both Gemini shards in https://github.com/databricks/unity-gateway/actions/runs/40733908631 (Oct 28) and again on Windows in run 43048607627 (Nov 11, after databricks#1595). ## Tests - `ruff check` / `ruff format --check` clean on `tests/test_e2e.py`. - Scratch pytest run with the CI flags (`--reruns 1 --only-rerun 'timed out after [0-9]+s'`), with `urlopen` patched to raise. A bare `TimeoutError` and a `URLError(socket.timeout)` each produce `RERUN`. A non-timeout `URLError` propagates unchanged. - Not run live. This pull request and its description were written by Isaac. --------- Co-authored-by: Isaac <no-reply@databricks.com>
What
Hardens the flakiest CI tests found in a 60-day analysis of
ci.yml(5,320 runs; job-level detail for the last 14 days). Over the last 14 days, 44% of completed runs had a failing blocking job, and 263 of 385 manual reruns over the 60 days ended green. This PR targets causes that are proven flakes (the job failed, then passed on a rerun of the same commit). It changes tests and CI only; no product code.tests/integration/utils/request_recorder.py). Handler threads are daemons, and__exit__closed the httpx client while a forward could still be streaming. That frees the socket descriptor while OpenSSL keeps using the old number, so a later connection in the same process had its bytes stolen. This producedResponse ended prematurely,SSL: RECORD_LAYER_FAILURE,incomplete chunked readandBad file descriptor. All 22 flaky CUJ 2 Claude failures ran right after a passing CUJ 2 Codex class. Teardown now waits for in-flight forwards before closing the client.tests/test_e2e.py,tests/conftest.py). These looped over the live catalog and failed as a whole when any one model misbehaved; the same test failed across 19–29 unrelated PRs. They are now parametrized, one case per model, from a cached catalog fetch shared withe2e_state.UCODE_TEST_WORKSPACEnothing touches the network.ci.yml). This addspytest-rerunfailuresand reruns once, only forTimeoutExpired,timed out after Ns,TIMEOUT after,TEMPORARILY_UNAVAILABLEandREQUEST_LIMIT_EXCEEDED.-rfERlists reruns.tests/AGENTS.mdrule 6.tests/integration/utils/sql.py). Each statement now gets 300 s instead of 180 s. The trace row is polled every 15 s for up to 300 s, instead of one query after a 30 s sleep. The tests still assert the row exists.test_smart_router_config_versionbecomes a per-agent preset routing test and a Claude status-band/toggle test.Not covered
task view reporting no running tasks within 180s, 17 of 43 flaky jobs) is a bug inwait_for_background_tasksintests/integration/utils/terminal.py. In 14 of 19 replayed screens the/taskspanel had already closed with all work finished. In the other 5, the panel showed✔ Completed, but the check never stayed true for the required 0.3 s. The split isolates this failure but does not fix it; that will be a follow-up.fix-model-services-probe-retry.Docs
tests/README.md,tests/integration/README.mdandtests/e2e_cuj/README.md: renamed the CUJ 4 and skill-toggle tests and updated their coverage descriptions.Tests
tests/test_tui_request_recorder.pycover recorder teardown.test_teardown_leaves_client_open_for_forward_that_outlives_drainfails on the old code and passes now.origin/mainon the dev machine (environment-related PTY, smart-router and installer tests; compared test by test).tyis clean onsrc/.This pull request and its description were written by Isaac.