Repository navigation
fix(client-python): raise CauraAPIError for non-JSON 2xx bodies - #2013
Merged
erni-a merged 2 commits intoOct 11, 2026
Merged
Conversation
Signed-off-by: Girisha <anamalagirisha@gmail.com>
erni-a
approved these changes
Oct 11, 2026
erni-a
left a comment
Contributor
There was a problem hiding this comment.
Thanks @Girisha1908. One helper, every entry point routed through it, the cause preserved, the status kept on the error, and the transport-error test updated to say what is now true. Merging in this pass; it shares test_client.py with #1991 and #2010 so expect me to update the branch before merge.
3 of 13 tasks
Merged
eldad-caura-ai
pushed a commit
that referenced
this pull request
Oct 11, 2026
🤖 I have created a release *beep* *boop* --- <details><summary>backend: 3.30.7</summary> ## [3.30.7](backend-v3.30.6...backend-v3.30.7) (2026-10-11) ### Bug Fixes * a bulk item's null weight gets the column default (L-97) ([#2036](#2036)) ([25bc89e](25bc89e)) * **client-python:** raise CauraAPIError for non-JSON 2xx bodies ([#2013](#2013)) ([d6580f7](d6580f7)) * **client-typescript:** raise CauraApiError for a non-JSON 2xx body ([#2014](#2014)) ([9ba140d](9ba140d)) * **clients:** parse HTTP-date Retry-After values ([#2010](#2010)) ([f575803](f575803)) * **events:** type pull_executor concretely in _hold_leases ([#1596](#1596)) ([#1698](#1698)) ([45dff80](45dff80)) * **release:** keep the core-operations version in sync ([#1717](#1717)) ([96a7907](96a7907)) * storage answers for the database it has (B28) ([#2033](#2033)) ([67a0a57](67a0a57)) </details> --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please). Signed-off-by: release-please[bot] <release-please[bot]@users.noreply.github.com> Co-authored-by: caura-deploy-bot[bot] <265395343+caura-deploy-bot[bot]@users.noreply.github.com>
1 task
eldad-caura-ai
added a commit
that referenced
this pull request
Oct 15, 2026
## Summary Bumps both SDK clients to **1.1.0** so the next publish carries what has merged since the last releases (O14). Neither client has been published since `caura-client` 1.0.3 on PyPI (tag `caura-client-v1.0.3`) and `@caura/client` 1.0.2 on npm (tag `caura-client-ts-v1.0.2`). Versions only: `clients/python/pyproject.toml` and `caura_client/_version.py`; `clients/typescript/package.json`, `package-lock.json` (its own two entries) and `src/version.ts`. ## What 1.1.0 carries Both clients: - #1807: refuse to send the API key over plain HTTP to another host - #2010: parse HTTP-date `Retry-After` values - #2012: `search()` keeps the response envelope (`SearchResult`, still a list or array), `recall()` sends `items_alias: false`, and `caller_agent_id` / `callerAgentId` lets a tenant-key client read as an agent (opt-in) - #2035: `stats()` - #2071: the memory map, spend and value methods Python: #2013 (`CauraAPIError` for a non-JSON 2xx body), plus #1775 and #1909's client parts. TypeScript: #2014 (`CauraApiError` for a non-JSON 2xx body), #1667 (typing and response timeouts). **Why minor:** new methods and fields. `search()` still returns a list subclass (an array in TypeScript), so existing callers keep working. One visible change: `recall()`'s `raw` no longer contains `items` unless `items_alias` is passed. **Server versions:** `stats()` needs a backend with #2035 (3.32.0 or later) and the memory map, spend and value methods need #2071 (3.33.0 or later). Against an older server those calls get a 404; everything else works as before. ## Release, after merge Each tag runs its publish workflow: - `caura-client-v1.1.0` → `publish-python-client.yml` (PyPI `caura-client`) - `caura-client-ts-v1.1.0` → `publish-npm-client.yml` (npm `@caura/client`) The `caura`, `caura-sdk` (PyPI) and unscoped `caura-client` (npm) packages depend on `caura-client>=1.0.0` / `@caura/client ^1.0.1`, so they pick up 1.1.0 without a release of their own. ## Test plan - [ ] CI: the Python client's version test (`__version__` agrees with `pyproject.toml`) and the TypeScript client's build and tests pass 🤖 Generated with [Claude Code](https://claude.com/claude-code) Signed-off-by: eldad-caura <eldad@caura.ai> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 of 2 tasks
eldad-caura-ai
added a commit
that referenced
this pull request
Oct 20, 2026
L-237: stats(), added after #2013 and #2014, read its body without the checks the other methods have. In Python a 2xx page that is not JSON (a captive portal, a proxy) escaped as a bare JSONDecodeError, and a JSON body that is not an object as AttributeError, though the README says catching CauraError covers HTTP failures. In TypeScript a body that is not an object came back as zero counters. Both now raise "stats response must be a JSON object", as search() and recall() do. L-238: retry_after could hold a delay nobody can wait on. Python's float() takes "nan", "inf", "-5" and digit strings past float range; time.sleep() raises on NaN and negatives and overflows on infinity, and asyncio.sleep(inf) never returns. TypeScript's Number() read "-5" as -5 and a blank header as 0. Both now report None/null unless the delay is finite and non-negative, as the collaboration client's own parser already drops negatives. Signed-off-by: eldad-caura <eldad@caura.ai> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
eldad-caura-ai
added a commit
that referenced
this pull request
Oct 20, 2026
…2116) ## Summary Two Low findings in the client SDKs, from the security audit's review of the client changes merged since its last recheck. Both are in the clients' response handling, so they share one PR. **L-237: `stats()` skips the response checks every other method has.** It was added in #2035, after #2013 and #2014 made a non-JSON 2xx body a `CauraAPIError`. - **Python:** `stats()` reads `response.json()` directly. A captive-portal or proxy page with a 200 status escapes as a bare `JSONDecodeError`, although the README says catching `CauraError` covers HTTP failures. A JSON body that isn't an object raises `AttributeError`. - **TypeScript:** `stats()` returns zero counters with a non-object `raw` for a body that isn't an object. `search()` and `recall()` in both clients reject such bodies with `"... response must be a JSON object"`. **L-238: `retry_after` can hold a value nobody can wait on.** - **Python:** `float()` accepts `nan`, `inf`, `-5`, and digit strings past float range (which become `inf`). A caller that sleeps on it fails: `time.sleep()` raises for NaN and negatives and overflows on infinity, and `asyncio.sleep(inf)` never returns. - **TypeScript:** `Number()` reads `-5` as -5 and a blank header as 0. The collaboration client's own parser (`caura_bus_core.retry.retry_after_seconds`) already rejects negatives. ## Fix - **Both clients:** `stats()` rejects a body that is not a JSON object with `"stats response must be a JSON object"`, the same check `search()` and `recall()` make. - **Python:** `stats()` reads its body through `_json()`, the helper from #2013, so a 2xx page that is not JSON raises `CauraAPIError` like every other call. - **Retry-After:** `retry_after` is kept only when it is finite and non-negative; otherwise it is `None`/`null`. TypeScript also treats a blank header as absent. Valid delays and HTTP-dates (past dates still give 0) are unchanged. ## Test plan - [x] **Tests alone failed in CI** (commit 944953f), all on the new cases: - Client (Python) CI, run [39404592562](https://github.com/caura-ai/caura/actions/runs/39404592562): 8 failed, 233 passed. The failures are `stats` in the non-JSON 2xx test (`JSONDecodeError`), the three non-object `stats` bodies (`AttributeError`), and `retry_after` for `-5`, `nan`, `inf` and a 400-digit value (`-5.0`, `nan`, `inf`, `inf`). - Client (TypeScript) CI, run [39404592529](https://github.com/caura-ai/caura/actions/runs/39404592529): 5 of 78 failed. They are the three non-object `stats` bodies (resolved instead of rejecting), and `-5` and a blank header (`-5` and `0` instead of `null`). - [ ] Both pass with the fix 🤖 Generated with [Claude Code](https://claude.com/claude-code) Signed-off-by: eldad-caura <eldad@caura.ai> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
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
When a 2xx response has a body that isn't JSON (a captive portal page, a misconfigured proxy), every method on the Python client raised a bare
json.JSONDecodeError. That isn't aCauraError, soexcept CauraError:didn't catch it. This PR raisesCauraAPIError(status, "response was not valid JSON")instead, with the original error kept as__cause__.Related Issue
Closes #1899
Type of Change
How Has This Been Tested?
Added
test_non_json_success_body_raises_api_errortoclients/python/tests/test_client.py. It returns a 200 with an HTML body and runs againsthealth,write,search,recall,get_documentandsubmit_interview. All six cases fail onmainand pass with this change.Ran the same checks as
client-python-ci.yml, on Linux:Checklist
ruff checkandruff format --checkpassmypypassespytestpasses locallyCHANGELOG.mdunder theUnreleasedsection (if user-facing)Additional Notes
test_transport_mapping_does_not_wrap_invalid_jsonintest_transport_errors.pyexpected the bareValueErrorthis PR removes. It now expectsCauraAPIError.