Skip to content

fix(rpcclient): handle non-dict error bodies from JSON-RPC 500 responses - #27

Merged
cshenry merged 1 commit into
cshenry:mainfrom
jplfaria:fix/rpcclient-non-dict-error
Jul 21, 2026
Merged

fix(rpcclient): handle non-dict error bodies from JSON-RPC 500 responses#27
cshenry merged 1 commit into
cshenry:mainfrom
jplfaria:fix/rpcclient-non-dict-error

Conversation

@jplfaria

Copy link
Copy Markdown

rpcclient.py:75 does raise ServerError(**err[\"error\"]) which requires err[\"error\"] to be a mapping. Well-behaved JSON-RPC servers return a dict there, but tutorial.theseed.org (and other backends under load) occasionally return a plain string or a list. The ** unpack then raises

TypeError: argument after ** must be a mapping, not str

which masks the real upstream failure and surfaces to callers as a completely unrelated exception. modelseed-api's failure_watcher has been auto-filing GitHub issues for this shape periodically.

Fix

Check isinstance(error_body, dict) before the ** unpack. When it's not a mapping, coerce into ServerError(\"Unknown\", 0, str(body)) - the same shape already produced for a non-JSON 500 or a JSON 500 without an error key. Callers now see a ServerError with the upstream body attached to .message instead of a TypeError with no context.

Tests

5 unit tests in tests/core/test_rpcclient.py:

  • dict error body → populated ServerError (happy path)
  • string error body → Unknown ServerError with body in .message (regression)
  • list error body → same shape as string
  • 500 with no error key → pre-existing Unknown branch, locked in
  • 500 with non-JSON content-type → pre-existing branch, locked in

All 5 pass locally.

Context

Companion to the modelseed-api Phase 3 bulk-reconstruction work (which loops the ModelSEEDpy annotate-fasta primitives directly). We've been catching this via the annotator's retry logic in modelseed-api, but the rpcclient shouldn't lose the upstream context in the first place.

🤖 Generated with Claude Code

`rpcclient.py:75` does `raise ServerError(**err["error"])` which
requires `err["error"]` to be a mapping. Well-behaved JSON-RPC servers
return a dict there, but tutorial.theseed.org (and other backends
under load) occasionally return a plain string or a list. The
`**` unpack then raises

    TypeError: argument after ** must be a mapping, not str

which masks the real upstream failure and surfaces to callers as a
completely unrelated exception. modelseed-api's failure_watcher has
been auto-filing GitHub issues for this shape.

Fix: check `isinstance(error_body, dict)` before the `**` unpack. When
it's not a mapping, coerce the body into `ServerError("Unknown", 0,
str(body))` - the same shape we already produce for a non-JSON 500 or
a JSON 500 without an `error` key. Callers see a `ServerError` with
the upstream body attached to `.message` instead of a `TypeError` with
no context.

5 unit tests in tests/core/test_rpcclient.py:
- dict error body -> populated ServerError (happy path)
- string error body -> Unknown ServerError with body in message (regression)
- list error body -> same shape as string
- 500 with no `error` key -> pre-existing Unknown branch, locked in
- 500 with non-JSON content-type -> pre-existing branch, locked in

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@cshenry
cshenry merged commit b758b44 into cshenry:main Jul 21, 2026
0 of 12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants