Skip to content

fix: Raise a Seam error for a success response that is malformed - #574

Open
razor-x wants to merge 1 commit into
mainfrom
claude/ruby-sdk-audit-xbhaia-invalid-response
Open

fix: Raise a Seam error for a success response that is malformed#574
razor-x wants to merge 1 commit into
mainfrom
claude/ruby-sdk-audit-xbhaia-invalid-response

Conversation

@razor-x

@razor-x razor-x commented Sep 1, 2026

Copy link
Copy Markdown
Member

Problem

SDK audit finding M3. A 2xx with an unexpected envelope (proxy rewrite, gateway maintenance page with a JSON content type, response-key rename) escaped the SDK's error hierarchy: generated routes did res.body["device"] and the resource loader raised a bare NoMethodError on nil or String, or returned nil silently. A 3xx passed straight through, since Faraday's RaiseError only covers 400–599, so a redirecting endpoint yielded nil instead of an error. The action-attempt poll crashed the same way mid-wait, and the paginator silently ended iteration when the pagination object was missing.

Fix

Ported from seamapi/python#638 and seamapi/php#475, with the same message shape as the other three SDKs:

Seam returned an invalid response for /devices/get: expected "device", which the response does not contain
Seam returned an invalid response for /devices/get: expected "device", got String instead of a response object
Seam returned an invalid response for /devices/list: expected "devices", got Hash instead of a list
Seam returned an invalid response for /devices/get: expected "device", got a 302 response instead of a success response
Seam returned an invalid response for /devices/list: expected "pagination", got String instead of a pagination object
  • New Seam::Http::InvalidResponseError with path and response_key.
  • New Seam::Http::Response.read / .read_list / .read_pagination in lib/seam/response.rb.
  • Template change: client-method.hbs reads every route's response through the helper, choosing read_list from the blueprint's resource_list response type. Regenerated diff is exactly the 96 resource unwraps, the 23 action-attempt unwraps, and one require per route file.
  • ActionAttemptResolver and the paginator use the same helper. The paginator's middleware now hands the body and request path to the paginator, which validates the pagination envelope, so a list route that returns no pagination raises instead of silently stopping.

A 2xx whose body is declared JSON but does not parse still raises Faraday::ParsingError from Faraday's JSON middleware, as before; that case is pinned in the spec.

Tests

New spec/seam_client/invalid_response_spec.rb (WebMock): missing key, wrong key, null body, string body, plain text, non-object under key, non-list under list key, 302, malformed poll response mid-wait, missing and non-object pagination through create_paginator. Every message is pinned exactly.

Revert-check against main: 11 failures, with the audit's symptoms (undefined method 'each' for nil, undefined method '[]' for nil, and "nothing was raised" for the redirect and pagination cases).

🤖 Generated with Claude Code

https://claude.ai/code/session_01SQW83gyXeUG61RDrHEky97


Generated by Claude Code

Every generated route read its resource straight out of res.body, so a
2xx response with an unexpected envelope, a proxy rewrite, a gateway page
with a JSON content type, or a renamed response key surfaced as a bare
NoMethodError on nil or String from deep inside the resource loader, or
as a silent nil. A 3xx passed through untouched because Faraday's
RaiseError only covers 400 through 599. The action attempt poll and the
paginator's pagination envelope failed the same way, the latter by
silently ending iteration.

Add Seam::Http::InvalidResponseError and a Seam::Http::Response helper
that reads the response key for an object, a list, or the pagination
envelope, naming the endpoint, the expected key, and what arrived
instead. Generate every route through it, and route the action attempt
poll and the paginator through the same helper.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SQW83gyXeUG61RDrHEky97
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