fix(client): send a JSON body on every body-less mutating request - #106
Conversation
…the API edge lets it through The API edge answers a POST, PUT, PATCH or DELETE without a JSON content type with a 403 HTML page before it reaches a worker (nominal#1575). `cloud disconnect` sent a body-less DELETE and failed with "Invalid JSON response from server (status 403)" (#105). The client now sends an empty object on any mutating request without a body of its own, which also retires the per-call workaround in `auth bind-run`. Closes #105 Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
There was a problem hiding this comment.
LGTM: Correct, minimal fix at the shared client with method-by-method tests; workaround removal in bind-run stays covered.
Approve — the fix is correct, minimal, and well tested. Reviewed head 52f8cde.
What it does: src/client/http.ts now resolves the payload once — caller's body, else undefined for GET, else {} for POST/PUT/PATCH/DELETE — so every body-less mutating request carries Content-Type: application/json and {}, which the API edge requires (nominal#1575). bind-run.ts drops its per-call body: {} workaround, now redundant.
Checks I ran:
- The payload rule uses the resolved
method(opts.method ?? 'GET'), so a default-GET request correctly stays body-less. HttpMethodis exactlyGET|POST|PUT|PATCH|DELETE, so the "GET is the only body-less method" rule is exhaustive — no HEAD/OPTIONS to worry about.- The dry-run and query/header paths are untouched; the only behavioral delta is the two-byte body plus header on previously body-less mutations, which the PR body correctly flags as ignored by the API.
- New test
test/client-http-body.test.tspins all four mutating methods, caller-supplied body pass-through, and the GET exception via a fetch stub with clean restore;bind-run's existing test still asserts the{}body per the PR. No tests removed or weakened; nodocs/reference/specs/in this repo, so no spec check applies.
On the "feedback wanted" question: agreed with the current shape — with HEAD/OPTIONS absent from the type, no further special-casing is warranted; if they're ever added, they should join GET in the body-less branch.
No findings.
|
Prod receipt on 0.2.39 (npm |
polylane cloud disconnectfailed with "Invalid JSON response from server (status 403)" because it sent a DELETE with no body. The shared HTTP client now sends an empty JSON object on every body-less POST, PUT, PATCH and DELETE, so any command can reach the API.Why: The Polylane API edge rejects a mutating request without a JSON content type with a 403 HTML page before a worker sees it (nominal#1575). #83 patched one call site; #105 is the next one, found while collecting Trigger.dev receipts on prod.
Where to look
{}; none does.body: {}workaround from fix(auth): send a JSON body on the bind-run POST so the API edge lets it through #83; its existing test still asserts the{}body, now supplied by the client.Feedback wanted: Whether to special-case any method besides GET. HEAD and OPTIONS are not in
HttpMethod, so the rule reads as "GET is the only body-less method".Risk: Every mutating CLI request now carries a content type and a two-byte body; the API ignores an empty object on routes without input. Rollback is a revert.
Verified: cli suite 540 pass; the new test's four mutating-method cases fail with the fix reverted; eslint and tsc clean.
Validation (4 criteria)
content-type: application/jsonand{}test/client-http-body.test.ts(4 cases), 6 pass at 52f8cde; 4 fail with the payload rule revertedtest/client-http-body.test.ts::a body given by the caller is sent as istest/client-http-body.test.ts::GET stays body-less with no content typeauth bind-runstill sends{}without its own workaroundtest/auth-bind-run.test.ts::sends a JSON content type and an empty object body so the API edge lets it throughFor agents
Prod repro on 0.2.38:
polylane cloud disconnect acc_… --yes --output json→{"error":{"code":1,"message":"Invalid JSON response from server (status 403)"}}; the same DELETE with the JSON header and{}returns 200 (2026-09-18T00:03:11Z). The generated API client is gitignored (npm run codegenregeneratessrc/client/client.ts), every generated operation goes throughrequest()insrc/client/http.ts.🤖 Generated with Claude Code