From c35ffd60c990ebaba9a5a29e52bc8c793e9d4ba1 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 19 Aug 2026 21:44:39 +0000 Subject: [PATCH] fix: Send the action attempt poll id as a query The resolver polled /action_attempts/get with the id in a JSON body on a GET, while the generated route for the same endpoint sends it as a query. A GET body is not carried reliably: any proxy, CDN, or load balancer that strips one breaks every wait loop, and it breaks it after the write has already been commanded. The body also skipped the serializing client's query handling, so _strict=true was never applied to the poll. Send a query instead, matching the generated route. The fake server reads GET bodies, so the wire shape is asserted with the recording client. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01HH3wdHh4Y6Wjyc5uHwk5iG --- src/Http/ResolveActionAttempt.php | 2 +- tests/WaitForActionAttemptTest.php | 43 ++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/src/Http/ResolveActionAttempt.php b/src/Http/ResolveActionAttempt.php index 4999d833..5139d9f9 100644 --- a/src/Http/ResolveActionAttempt.php +++ b/src/Http/ResolveActionAttempt.php @@ -100,7 +100,7 @@ private static function get_action_attempt( ): ActionAttempt { $res = Body::decode( $client->request("GET", "/action_attempts/get", [ - "json" => (object) ["action_attempt_id" => $action_attempt_id], + "query" => ["action_attempt_id" => $action_attempt_id], ]), ); diff --git a/tests/WaitForActionAttemptTest.php b/tests/WaitForActionAttemptTest.php index 84eeb9f3..601433cf 100644 --- a/tests/WaitForActionAttemptTest.php +++ b/tests/WaitForActionAttemptTest.php @@ -11,6 +11,7 @@ use Seam\Resources\ActionAttempt; use Seam\Seam; use Tests\Support\FakeSeamConnectTestCase; +use Tests\Support\RecordingClient; final class WaitForActionAttemptTest extends FakeSeamConnectTestCase { @@ -360,6 +361,48 @@ public static function invalidWaitOptions(): array * than the route client, so enabling the option on the route that reads * action attempts cannot recurse. */ + public function testPollSendsTheIdAsAQueryNotABody(): void + { + $recorder = new RecordingClient([ + RecordingClient::json(200, [ + "action_attempt" => [ + "action_attempt_id" => "aa_1", + "status" => "pending", + ], + ]), + RecordingClient::json(200, [ + "action_attempt" => [ + "action_attempt_id" => "aa_1", + "status" => "success", + ], + ]), + ]); + + $seam = Seam::from_api_key( + "seam_apikey_token", + endpoint: "https://example.com", + guzzle_options: $recorder->guzzle_options(), + retries: 0, + ); + + $resolved = $seam->action_attempts->get("aa_1", [ + "timeout" => 5.0, + "polling_interval" => 0.01, + ]); + + $this->assertSame("success", $resolved->status); + + $poll = $recorder->request(1); + + $this->assertSame("GET", $poll->getMethod()); + $this->assertSame("/action_attempts/get", $poll->getUri()->getPath()); + $this->assertSame( + "action_attempt_id=aa_1&_strict=true", + $poll->getUri()->getQuery(), + ); + $this->assertSame("", (string) $poll->getBody()); + } + public function testActionAttemptsGetDoesNotRecurse(): void { $seam = $this->seam(wait_for_action_attempt: false);