fix: unbounded embeddings wait (timeout=None) and typed 502 for allowlist misses - #1269
seonghobae wants to merge 1 commit into
Conversation
- ProviderEmbeddingBatchBackend.wait accepts timeout=None again and treats it (like inf) as no application deadline. The default /v1/embeddings path forwards ModelClient.timeout (None by default) and math.isfinite(None) raised TypeError, which member failover turned into 503 embeddings_unavailable. - _validate_allowlisted_provider raises the typed, non-retryable ProviderUpstreamError(client_status=502) from d26fa13 again on top of the EgressWeave validator (#1046), instead of a plain RuntimeError that surfaced as HTTP 500. - Add tests/test_embeddings_unbounded_wait.py covering the backend, complete_embeddings_batch, and the default server path.
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: trueThanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
seonghobae
left a comment
There was a problem hiding this comment.
Verified complete successor carryover: #1266 exact ca5efdc023cad70210f5b1d2114a1aa417009c81 owns both production repairs and all valid executable requirements. Existing successor tests cover direct unbounded/infinite/coordinator behavior and allowlist classification/failover; 2eceb647… adds the remaining finite-deadline and default HTTP /v1/embeddings timeout=None fixtures. Exact successor blob 8db8c9ec… compiles and its isolated finite test passes. Retiring this duplicate lane does not assert hosted GREEN, approval, protected integration, release, or delivery completion.
Summary
Two runtime regressions, fixed test-first. This PR is based on
origin/main@5665b0ad.1. Default
/v1/embeddingspath:math.isfinite(None)raisesTypeError/v1/embeddingscallscomplete_embeddings_batch(..., wait_timeout=orchestrator.client._resolved_model_timeout(agent)), and that value isNoneby default (the no-implicit-deadline default).complete_embeddings_batchalways forwards it tobackend.wait(job, timeout=wait_timeout). Its docstring already defineswait_timeout=Noneas "wait without an application deadline".ProviderEmbeddingBatchBackend.wait(batch_routing.py) didmath.isfinite(timeout), which raisedTypeError: must be real number, not NoneType. Member failover swallowed the error, so clients saw503 embeddings_unavailableon every non-mock://embedding request.284447fcintroducedwait(timeout: float | None)withNone= unbounded.56a34abcaddedinf->Nonehandling.isfiniteform, droppingNone.test_unbounded_synchronous_embedding_waits_for_provider_completionlives intests/test_provider_embedding_batch_backend.py, which currently fails to collect on main. That's why the regression was missed.wait(..., timeout: float | None).Noneand non-finite values both meanthreading.Event.wait(timeout=None)(block until terminal), matching the documentedwait_timeout=Nonesemantics and the existinginfhandling. Finite deadlines are unchanged.booltimeouts. NaN was already treated as unbounded, and a negative value returns immediately after a poll. This PR doesn't add new rejections; it only restoresNone.2. Allowlist misses return HTTP 500 instead of the typed 502
d26fa132classified an allowlist miss asProviderUpstreamError(error_code="provider_connection_error", client_status=502, retryable=False, transport="chat").ModelClient._validate_allowlisted_providerwith a plainRuntimeError, which escaped provider failover as HTTP 500.ModelClient._provider_host_not_allowlisted(agent)helper returns the typed error again, and it is raised from both EgressWeave branches (except EgressNotAllowedError as exc: ... from excandvalidated is None)."<agent> provider host is not allowlisted", which never names the host) are unchanged.transportviaclassify_provider_failure.Tests
tests/test_embeddings_unbounded_wait.py(5 tests; fully mocked, no network or live provider calls). It goes in a new file so it doesn't touchtests/test_provider_embedding_batch_backend.py, which fix(tests): restore main's test signal; re-apply dropped OpenRouter availability/quality split #1266 edits.wait(timeout=None)blocks until terminalinfstill blocks until terminalcomplete_embeddings_batchwith the defaultwait_timeoutcompletes/v1/embeddingspath, withModelClient.timeout=None, returns 200 andbackend.waitseestimeout=Nonetests/test_provider_reliability.py::test_unallowlisted_provider_host_is_classified_upstream_error::test_passthrough_allowlist_failure_reports_passthrough_transport::test_free_model_exhausted_allowlist_pool_fails_closed_as_502RED on main (source identical to
origin/main, new test file added)Result: 6 failed, 2 passed.
TypeError: must be real number, not NoneTypex2503 != 200RuntimeError: blocked_agent provider host is not allowlistedx2RuntimeError: all 2 candidate agents failed for role=workerThe 2 passes are the finite-deadline and
infguards.GREEN with this PR
Same command: 8 passed.
Full suite versus main
Command:
uv run --no-sync python -m pytest -q -ra -p no:cacheprovider --continue-on-collection-errors. Main is already red and is being handled separately.5665b0adOverlap with open PRs
I checked the diffs of #1262, #1264, #1265 and #1266 first.
orchestrator.pyhunk here is around L3972-4002 (_validate_allowlisted_provider). The open PRs touchorchestrator.pyat: refactor(reporting): move commercial report generators out of TaskOrchestrator (ADR 0124 step 1) #1262 imports/~L565/>=L12964; fix(conduct): answer with the synthesis and judge that answer (scoring unchanged) #1264 ~L9596; fix(judge): classify judge failures (misconfigured / unavailable / ordinary rejection) without penalising candidates #1265 L9235-9337 and L11920-12089; fix(tests): restore main's test signal; re-apply dropped OpenRouter availability/quality split #1266 L5571.batch_routing.py,tests/test_provider_reliability.py, or the new test file.Not verified locally
decision_receiptextension isn't built in the local venv (same as the main baseline).if:conditions.Verified complete successor carryover — 2026-09-27
Canonical successor #1266 exact
ca5efdc023cad70210f5b1d2114a1aa417009c81completely carries this PR's valid delta.timeout=None/non-finite wait contract and typed, non-retryable allowlist 502 boundary.2eceb6474c2904ae308991e89595b1f44c852940adds the remaining distinct finite-deadline and real default HTTP/v1/embeddingsfixtures; Gap receiptca5efdc023cad70210f5b1d2114a1aa417009c81records the mapping.8db8c9ec80757d25cd715aa3acaf191c7f613640compiles and the isolated finite-deadline case passes. Fresh successor hosted Checks and independent approval remain open gates.This PR is retired only because every valid production requirement and executable contract has a verified successor. It is not merged, released, or treated as completed delivery evidence.