Skip to content

fix: Send the action attempt poll id as a query - #467

Merged
razor-x merged 1 commit into
betafrom
claude/php-audit-uoa7nb-m1-poll-query
Aug 19, 2026
Merged

fix: Send the action attempt poll id as a query#467
razor-x merged 1 commit into
betafrom
claude/php-audit-uoa7nb-m1-poll-query

Conversation

@razor-x

@razor-x razor-x commented Aug 17, 2026

Copy link
Copy Markdown
Member

Fixes finding M1 from the cross-SDK audit.

Rebased onto beta at a2cd8ce (4.0.0-beta.13), so the conflict is gone. Re-checked against beta after #478 changed HTTP verbs: the generated /action_attempts/get route is still GET with a query, so the mismatch this fixes is still real.

The problem

ResolveActionAttempt::get_action_attempt polls with the id in a JSON body on a GET:

$client->request("GET", "/action_attempts/get", [
    "json" => (object) ["action_attempt_id" => $action_attempt_id],
]);

The generated route for the same endpoint sends a query (src/Routes/ActionAttemptsClient.php):

$this->client->request("GET", "/action_attempts/get", [
    "query" => $request_payload,
]);

Two consequences:

  • 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 door has already been commanded, so the caller sees a failure for a write that succeeded.
  • _strict=true was never applied to the poll. SerializingClient only adds it to a query, so the poll skipped the schema-aware parsing every other read gets.

The fake server reads GET bodies happily, which is why no existing test caught it.

The fix

Send a query, matching the generated route. The poll now goes out as GET /action_attempts/get?action_attempt_id=…&_strict=true with an empty body.

Tests

testPollSendsTheIdAsAQueryNotABody asserts the poll's method, path, query string, and empty body via RecordingClient — the fake can't show this. Against current beta it fails with the query string empty:

-'action_attempt_id=aa_1&_strict=true'
+''

Full suite: 229 tests green, psalm clean, npm run lint clean. The deprecations in the run are the resource-constructor issue from #473, not yet on beta.

Note on scope

The audit's preferred form is polling through the generated ActionAttemptsClient with wait_for_action_attempt: false, as the JS SDK does. That requires threading the route client through codegen and touches every generated route; this PR fixes the wire shape only. Happy to follow up with the injection version if you'd rather have it.


Generated by Claude Code

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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HH3wdHh4Y6Wjyc5uHwk5iG
@razor-x
razor-x force-pushed the claude/php-audit-uoa7nb-m1-poll-query branch from 313e5e6 to c35ffd6 Compare August 19, 2026 21:44
@razor-x
razor-x merged commit 17a8c32 into beta Aug 19, 2026
15 checks passed
@razor-x
razor-x deleted the claude/php-audit-uoa7nb-m1-poll-query branch August 19, 2026 22:15
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