Repository navigation
Conversation
Adds clientlibs/CLIENT_LIB_PARITY_AUDIT.md, an audit of the JS, Java and Python client libs against each other and the backend /v6 API, with a phased reconciliation plan. Implements Phase A: fixes that need no API or product decisions. Java: - markDecisionPoint sends the cached assignment's experimentId and condition id (all overloads, plus Assignment.markDecisionPoint), so the server marks the experiment the user was actually assigned instead of re-selecting one - rotate assignedFactor alongside assignedCondition on within-subjects marks - populate assignment/feature-flag caches before invoking onSuccess, so calls chained from inside the callback see a warm cache - null-safe callbacks and properties map; null-safe target lookup - first TestNG suite (src/test/java) against an in-process HttpServer JS: - DefaultHttpClient: retries never ran (axios rejects non-2xx before the retry branch); now retries network errors and 5xx with backoff, fails fast on 4xx, and sets a 5s default timeout - ISendRewardResponse gains mode/success - markDecisionPoint no longer throws when the assignment cache is null - fix 3 pre-existing ApiService mark tests missing the context field Python: - _sync methods call ApiService's sync implementations instead of asyncio.run(), so they work inside a running event loop - reuse pooled httpx clients per UpgradeClient; add close()/aclose() and sync/async context manager support Docs: root CLAUDE.md lists the python lib (not a nonexistent csharp lib); java/CLAUDE.md documents tests and configurable timeouts. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Automatic retries can duplicate non-idempotent reward submissions and distort assignment counts, while Python cross-loop reuse abandons unclosed connection pools.
2 open issues
What changed in this PR
This PR documents differences among UpGrade’s JavaScript, Java, and Python clients and implements Phase A correctness fixes against the existing /v6 API, without backend changes.
Changes:
- Java adds cached experiment metadata to marks, aligns factor rotation, and improves callback and null handling.
- JavaScript updates retry handling, adds a default timeout, and handles missing assignment caches.
- Python uses native synchronous requests and pooled HTTP clients with lifecycle methods; tests and documentation support the changes.
| File | Description |
|---|---|
clientlibs/python/tests/test_client.py |
Tests synchronous calls, connection reuse, and cleanup. |
clientlibs/python/src/upgrade_client_lib/client.py |
Uses sync API methods and exposes lifecycle management. |
clientlibs/python/src/upgrade_client_lib/api_service.py |
Pools HTTP clients and adds cleanup methods. |
clientlibs/js/src/types/Interfaces.ts |
Adds reward response mode and success fields. |
clientlibs/js/src/DefaultHttpClient/DefaultHttpClient.ts |
Revises retries and adds a five-second timeout. |
clientlibs/js/src/DefaultHttpClient/DefaultHttpClient.spec.ts |
Tests timeout configuration and retry behavior. |
clientlibs/js/src/DataService/DataService.ts |
Guards assignment lookup against a null cache. |
clientlibs/js/src/DataService/DataService.spec.ts |
Tests lookup before assignments are loaded. |
clientlibs/js/src/ApiService/ApiService.spec.ts |
Corrects expected mark payloads to include context. |
clientlibs/java/src/test/java/org/upgradeplatform/client/ExperimentClientTest.java |
Adds HTTP-backed client regression tests. |
clientlibs/java/src/main/java/org/upgradeplatform/utils/APIService.java |
Allows a null properties map. |
clientlibs/java/src/main/java/org/upgradeplatform/responsebeans/Assignment.java |
Includes experiment and condition IDs in marks. |
clientlibs/java/src/main/java/org/upgradeplatform/client/ExperimentClient.java |
Updates mark metadata, rotation, caching, and null handling. |
clientlibs/java/CLAUDE.md |
Documents tests and configurable timeouts. |
clientlibs/java/.gitignore |
Ignores TestNG output. |
clientlibs/CLIENT_LIB_PARITY_AUDIT.md |
Records client differences and phased reconciliation work. |
CLAUDE.md |
Corrects the client-library directory listing. |
Review effort: Balanced
Comment on lines
+84
to
+85
| const isRetryable = | ||
| (statusCode === undefined || statusCode >= 500) && !this.skipRetryOnStatusCodes.includes(statusCode); |
Comment on lines
+112
to
+114
| if self._async_client is None or self._async_client_loop is not loop: | ||
| self._async_client = httpx.AsyncClient(timeout=self._timeout) | ||
| self._async_client_loop = loop |
Collaborator
Author
|
Superseded by #3408. The head branch was renamed from |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Summary
The JS, Java, and Python client libs have drifted apart. This PR adds
clientlibs/CLIENT_LIB_PARITY_AUDIT.md, an audit of all three against each other and the backend/v6API with a phased reconciliation plan. It also implements Phase A: the correctness fixes that need no API or product decisions. No backend changes.Java
markDecisionPointnow sendsexperimentIdand conditionidfrom the cached assignment, across all overloads andAssignment.markDecisionPoint(). Previously Java sent onlyconditionCode, so the server re-ran experiment selection and could mark a different experiment when several share a decision point.assignedFactoralongsideassignedCondition.onSuccessruns. Previously a call chained from inside the callback (e.g. a mark) could hit a cold cache.propertiesmap, null-safe target lookup.src/test/java, TestNG). It runsExperimentClientagainst an in-processHttpServer.JS
DefaultHttpClientnever retried: axios rejects non-2xx responses before the retry branch runs, and that branch would have retried 4xx rather than 5xx anyway. It now retries network errors and 5xx with backoff, fails fast on 4xx, and sets a 5s default timeout.ISendRewardResponsegainsmode/successto match the backend.markDecisionPointno longer throws when the assignment cache is null (e.g. after a failed/assign).ApiServicemark tests that already fail ondev: their expected bodies were missingcontext.Python
_syncmethods useApiService's real sync implementations instead ofasyncio.run(), so they work inside a running event loop (Jupyter, async frameworks).UpgradeClient. Addsclose()/aclose()andwith/async withsupport.Docs
CLAUDE.mdnow lists the Python lib in place of a non-existent C# lib.java/CLAUDE.mdnow documents the tests and the timeouts, which are configurable viaproperties.Behavior change to review
The JS default client effectively made zero retries before. It now makes up to 3, including for non-idempotent
POST /markand/log. If a request reached the server but the response was lost, that can write duplicates. The audit flags the same issue for Java. We could restrict JS retries to requests that are safe to repeat if preferred.Testing
jest: 95/95 pass, ESLint/Prettier clean. The newDefaultHttpClient.spec.tsfails against the old code.pytest: 223/223 pass;ruff checkandmypyclean.ExperimentClientTest: 6/6 pass, 15 consecutive runs. These were run via TestNG directly because the local Maven mirror couldn't resolve the surefire TestNG provider;mvn testhasn't been run yet.Not in this PR (needs decisions; see audit §5)
sendReward, JSawaitResultonsendReward, feature-flag session groups for Java/Python, removing dead endpoints (JSlogCaliper, JavaaddGroupMetrics/addSingleMetrics, which hit routes the backend no longer serves).init()cache clearing, retry/timeout policy, argument order.🤖 Generated with Claude Code