Repository navigation
Fix the tests that fail against staging on every run - #2079
Conversation
The `unit-data` and `integration` jobs have failed on develop, and so on
every pull request, for five separate reasons.
MEA prediction import is gone from the backend. The prediction import
service rejects every import of that type ("9 FAILED_PRECONDITION: MEA
prediction import has been deprecated"), so the nine tests that call
`ModelRun.add_predictions()` are skipped, with that reason. The tenth test
in the file only reads a model run; it passes and stays enabled.
The embedding fixture could not heal itself. The staging organization is
at the cap of 10 custom embeddings. The fixture sweeps stale ones and
retries when it hits the cap, but only inside `except LabelboxError`, and
since `Client.create_embedding` became a GraphQL mutation the cap arrives
as `MalformedQueryException`, which is not a `LabelboxError`. So the sweep
never ran, and every test that needs an embedding errored at setup: three
in test_embedding.py, test_export_embeddings_custom and test_mmc_all. The
helper now catches both, for the create and for the delete of a stale
embedding that another job has already removed.
Validation messages are masked. Deployed API environments replace the
message of every GraphQL validation error with "GraphQL validation error".
`test_semantic_error` and `test_query_complexity_error` expected the
detailed text; they now accept the masked message, or the detailed one from
an API that does not hide schema details. Nothing else is accepted.
Two tests raced the backend. A global key is not free the moment its data
row is deleted (the import checks keys against the search index, which
drops the row a little later), and three labels are not listed the moment
they are created. Both tests now wait for the condition they depend on.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A newer formatter than the one CI runs had restyled one statement that this change does not touch. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Both failed once in the first run with the other fixes in place. `test_update_project_resource_tags` compared the number of resource tags in the organization before and after creating one (346 > 347). The suite runs for five Python versions at once against one organization, creating and deleting tags, so the count can go either way. It now looks for the two tags it created. `test_model_run_project_labels` read the first label of each data row from a model run export taken straight after `upsert_labels`. The export catches up a little later, and until then a data row comes out with no label for the run (IndexError). It now exports again while any is missing, for up to three minutes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`Client.is_feature_schema_archived` raised "Failed to get the feature schema archived status." for any answer other than 200 or 404, without the status or the body. `test_feature_schema_is_archived` failed that way once against staging and the log could not say why (a rate limit, or a server error). The message now carries both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Six jobs failed in the last run, in three ways. None is a defect of the code under test; all come from ten jobs of 32 workers using one staging user at once. Rate limit. The API counts requests per user in a fixed 60 second window and answers 429 beyond it. The SDK raises ApiLimitError and does not retry, and one rerun after 5 seconds does not outlast the window. The test client now waits and resends a request answered with 429, at the session level so that REST calls in teardown are covered too. It prints when it waits. A project without an editor. createProject sometimes returns a project whose server-side setup did not finish. It has no editor, and connect_ontology then fails with "Unsupported labeling frontend". The test client creates such a project again, and prints the ID of the one it discards so the API's logs can be searched for it. test_model_run_delete took the newest model in the organization instead of its own. Another worker's model can be deleted between listing it and reading its runs (TypeError in pagination), and the test deleted that other worker's model run, failing its setup. It now uses the model its fixtures created. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.
Reviewed by Cursor Bugbot for commit 737d0b6. Configure here.
| project.delete() | ||
| # Jitter, so workers that failed together do not retry together. | ||
| time.sleep(randint(10, 30) / 10) | ||
| return super()._create_project(input) |
There was a problem hiding this comment.
Missing editor deletes healthy projects
Medium Severity
IntegrationClient._create_project treats a missing labeling frontend as a failed create and deletes the project. That frontend is not in the create payload, so this is a second read with no wait. A project whose editor is not visible yet is destroyed, and the third create is returned without the same check, so the caller can still get a project with no editor.
Reviewed by Cursor Bugbot for commit 737d0b6. Configure here.
| # Without a trace in the log a wait only looks like a slow test. | ||
| print(f"API rate limit reached, resending in {wait} seconds") | ||
| time.sleep(wait) | ||
| return send(request, **kwargs) |
There was a problem hiding this comment.
Rate-limit retries wake up together
Medium Severity
wait_out_rate_limit sleeps a fixed time on every 429, with no jitter. Every CI job uses the same API key, so jobs that hit the cap together wait the same Retry-After or the same 20 seconds and resend together. That recreates the rate limit this wrapper is meant to ride out.
Reviewed by Cursor Bugbot for commit 737d0b6. Configure here.


Description
The
unit-dataandintegrationjobs fail ondevelop, and so on every pull request, on all five Python versions. The same tests fail each run. This fixes them. Tests only, apart from one error message in the SDK (last row of the table).There are eleven separate causes, each read from the tracebacks in the CI logs of #2078 and checked against the code.
tests/data/annotation_import/test_mea_prediction_import.py9 FAILED_PRECONDITION: MEA prediction import has been deprecated.tests/integration/test_embedding.py,test_export_embeddings_custom,test_mmc_all(all ERROR at setup)embeddingfixture could not heal itself. It sweeps stale embeddings and retries when it hits the cap, but only insideexcept LabelboxError. SinceClient.create_embeddingbecame a GraphQL mutation (#2074) the cap arrives asMalformedQueryException, which is not aLabelboxError, so the sweep never ran.tests/embedding_cleanup.pycatches both, for the create and for the delete of a stale embedding that another job has already removed. Unit tests cover both.test_client_errors.py::test_semantic_error,::test_query_complexity_errorGraphQL validation error. The tests expected the detailed text.test_data_rows.py::test_data_row_delete_and_create_with_same_global_key(2 of 5 jobs)test_label.py::test_label_bulk_deletion(1 of 5 jobs)test_project.py::test_update_project_resource_tags(1 job, seen once the tests above passed)test_mea_prediction_import.py::test_model_run_project_labels(1 job, same)test_ontology.py::test_feature_schema_is_archived(1 job, same)Client.is_feature_schema_archivedraised "Failed to get the feature schema archived status." for an answer that was neither 200 nor 404. The SDK dropped the status and the body, so the log cannot say whether it was a rate limit or a server error.src/.ApiLimitError: You have exceeded 15000 requests in a 60 second time windowtests/conftest.py) waits and resends a request answered with 429, up to four times, at the level of the HTTP session so that REST calls in teardown are covered. It prints when it waits.MalformedQueryException: Unsupported labeling frontendcreateProjectsometimes returns a project with no editor attached, when many are created at once.connect_ontologythen tries to attach the first editor named "Editor", which this organization's API refuses. Seventeen tests took that path in one run and all seventeen needed their rerun.test_model_run.py::test_model_run_delete, and the setup oftest_model_run_statusTypeErrorin pagination), and the test deleted that other worker's model run.test_issue_management.py::test_get_issues_with_status_filtererrored in one job on a rate-limit response during setup. That is not a defect of the test and it is not changed.What reviewers should know
Public API that no longer works. This PR does not touch it:
ModelRun.add_predictions()andMEAPredictionImport.create(),create_from_objects(),create_from_file()andcreate_from_url()always raise the error above.ModelRun.upsert_predictions_and_send_to_project()callsadd_predictions()first, so it fails the same way. Its tests were already skipped, without a reason.model_run.add_predictions: all eight underexamples/prediction_upload/, andcustom_metrics_basics,custom_metrics_demoandmodel_predictions_to_projectunderexamples/model_experiments/.These want a deprecation notice or a clearer error, and a changelog entry. That is a decision for the SDK's owners, so it is left out of a test fix.
test_query_complexity_errorcan no longer tell complexity apart. Where messages are masked, exceeding the complexity limit raisesInvalidQueryError, like any other validation failure.ValidationFailedErroris only reachable against an API that shows schema details. The test accepts either, each with its own message pinned.A project can come back from
createProjectwithout an editor. This is on the backend, and no test change removes it. Project creation catches an error in its post-commit setup and returns the project anyway, withcreationSetupFailed: true, a field the SDK does not request. Which setup step fails under concurrent creation is not established: the tests only see that the project has no editor a second later. The test client printsProject <id> has no editor, creating it againeach time, so the API's logs can be searched for those IDs.The rate-limit wait is back-pressure, not less traffic. If the budget of the shared user is short, the suite now slows down to it and says so in the log, where it used to fail. Worst case for one request is four waits of about a minute.
Whether the embedding tests go green depends on what holds the ten slots. The sweep only removes embeddings this suite created (
sdk-int-ci-v2-…) that are more than 12 hours old. If something else in the staging organization holds them, the cap stays, and the manual cleanup workflow is needed.How it was verified
No staging credentials were used. Each cause was read from the five logs of each job, and each fix was reviewed independently against those logs.
tests/unit/test_embedding_cleanup.pypasses (21 tests, three added). CI on this pull request is the real test: the first run with the first five fixes turned those tests green and showed the next three, each in one job out of ten. The run after that showed the last three. The two additions to the test client were also exercised against fakes: a 429 followed by a 200, a limit that never lifts, and a project that comes back without an editor once, and every time.Type of change
All Submissions
🤖 Generated with Claude Code
Note
Low Risk
Primarily test and CI harness changes; the SDK change only improves error diagnostics on a failure path.
Overview
Stabilizes staging CI for the Labelbox Python SDK by hardening integration test infrastructure and aligning assertions with deployed API behavior. Almost all changes are in tests; the only SDK tweak enriches the
LabelboxErrorfromis_feature_schema_archivedwith HTTP status and response body when the REST call is neither 200 nor 404.Integration client (
conftest) now retries on 429 (shared API key across parallel jobs) and re-creates projects whencreateProjectreturns one without a labeling editor. MEA prediction import tests are skipped because the backend rejects that flow as deprecated; the remaining model-run export test polls until labels appear in export.Flaky / environment-specific fixes: embedding fixture healing catches
MalformedQueryExceptionfor the custom-embedding cap (not onlyLabelboxError); GraphQL validation tests accept the masked"GraphQL validation error"message on staging; data-row tests wait for global-key reuse after delete; label/project/model-run tests avoid org-wide counts and use ID-based or wait/retry patterns under concurrent CI.Unit coverage for embedding cleanup is extended for the new exception types and client path.
Reviewed by Cursor Bugbot for commit 737d0b6. Bugbot is set up for automated code reviews on this repo. Configure here.