fix: Raise a Seam error for a success response that is malformed - #475
Merged
Merged
Conversation
razor-x
force-pushed
the
claude/php-audit-uoa7nb-m3-unexpected-response
branch
from
August 19, 2026 21:30
bcb65c6 to
fce92c7
Compare
Generated methods read the resource straight off the decoded envelope, with no guard on any of the unwrap sites. A 200 whose body had been rewritten or truncated on the way back, by a proxy, a gateway maintenance page carrying a JSON content type, or a load balancer, left the read as null and the failure surfaced from the method's return type: TypeError: DevicesClient::get(): Return value must be of type Device, null returned A list endpoint got array_map(): Argument #2 must be of type array. Both are an Error rather than an Exception, so neither is caught by catch (\Exception) nor by catch (SeamException), and neither says what was wrong with the response. Add InvalidResponseError, a SeamException, and read the envelope through Body::read and Body::read_list, which raise it naming the endpoint and the key. Keeping the guard in one place leaves the generated call sites a single call rather than a block repeated at every one. The action attempt poll read the same way and raised a bare UnexpectedValueException, which was equally invisible to a Seam catch block. It now raises the same error. The malformed-response tests covered only 500s, which never reach the unwrap. Add the malformed 200 cases: a missing key, the wrong key, a body that is not an object, an empty body, unparseable JSON, an HTML gateway page, a list key holding something that is not a list, and a malformed poll response. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HH3wdHh4Y6Wjyc5uHwk5iG
razor-x
force-pushed
the
claude/php-audit-uoa7nb-m3-unexpected-response
branch
from
August 19, 2026 22:25
fce92c7 to
fae0706
Compare
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.
Fixes finding M3 from the cross-SDK audit (the PHP half).
Renamed to
InvalidResponseErrorper review, and rebased ontobetaat4ee0b79(4.0.0-beta.16).The problem
Generated methods read the resource straight off the decoded envelope, unguarded, at every unwrap site:
A 200 whose body was rewritten or truncated on the way back — a proxy, a gateway maintenance page served with a JSON content type, a load balancer — leaves that read as
null, and the failure surfaces from the return type instead:List endpoints get
array_map(): Argument #2 ($array) must be of type array, string given.Both are an
Error, not anException— invisible tocatch (\Exception)and tocatch (SeamException), despiteSeamException's stated promise that one catch block covers everything the SDK raises. And neither says anything about what was wrong with the response.The fix
Seam\InvalidResponseError implements SeamException, raised from two new helpers onBody:Keeping the guard in one tested place leaves each generated call site a single call rather than a repeated block. The error carries
getPath()andgetKey(), and its message names both:ResolveActionAttemptread the same way and raised a bare\UnexpectedValueException— equally invisible to a Seam catch block. It now raises the same error.grep -rn '\$res->' src/Routesreturns 0.Tests
tests/MalformedResponseTest.phpcovered only 500s, which never reach the unwrap at all — the audit's "only php has a malformed-response file, and it covers only 500s". Eight new cases, all failing againstbetawith theTypeErrors above (verified: 7TypeErrors plus the poll case):betaTypeError: … Device, null returneddevicesfordevice)TypeError: array_map(): Argument #2 …SeamExceptionEach asserts the error is a
SeamExceptionand thatgetPath()/getKey()identify the endpoint and key.Full suite: 241 tests green, zero deprecations, psalm clean,
npm run lintclean.npm run generateproduces exactly the committed diff.Scope
M3 is a cross-SDK finding; this is the PHP part. It does not change how a non-2xx response without a Seam error envelope behaves — that still raises the underlying Guzzle exception, which is the existing documented behaviour and is covered by the pre-existing tests. README updated to draw the distinction.
Generated by Claude Code