Skip to content

fix: clarify non-JSON 5xx responses as intermediary/gateway errors#32

Merged
ysyneu merged 1 commit into
mainfrom
fix/gateway-error-msg
Jul 21, 2026
Merged

fix: clarify non-JSON 5xx responses as intermediary/gateway errors#32
ysyneu merged 1 commit into
mainfrom
fix/gateway-error-msg

Conversation

@ysyneu

@ysyneu ysyneu commented Jul 21, 2026

Copy link
Copy Markdown
Collaborator

What broke in prod

Yesterday a long-running request hit an ALB in front of the Flashduty API. The ALB itself timed out the request (its own timeout, not the API's) and answered with an HTML 504 error page instead of forwarding the request through. doMethod tried to parse that HTML as the JSON envelope, failed, and reported:

flashduty: malformed response (http 504, request_id ): invalid character '<' looking for beginning of value

That message reads exactly like a Flashduty API bug ("the API returned broken JSON"). Both a human and an AI agent looking at that error jumped to "the API has a contract bug" instead of the real cause: an intermediary cut the request off before the API ever answered.

Fix

Split the non-JSON-body failure in doMethod by status class:

  • Non-2xx + non-JSON body (the prod case): the Flashduty API always answers in JSON, so a non-JSON body on a failure status can only have come from an intermediary — gateway, load balancer, or proxy. New message names the status, says explicitly it's not from the Flashduty API, notes whether a request id was present, gives the actionable hint (retry / split long-running requests into smaller batches), and echoes a %q-quoted snippet of the actual body so the real error page is visible:

    flashduty: HTTP 504 returned by an intermediary, not the Flashduty API (non-JSON body, no request id) — the request likely exceeded a gateway/proxy timeout; retry, or split long-running requests into smaller batches. body: "<html><body>504 Gateway Time-out</body></html>"
    
  • 2xx + non-JSON body (CSV/file export endpoints): unchanged — still surfaced as Response.Raw with no error.

  • Non-2xx + valid JSON error envelope: unchanged — still maps to *ErrorResponse exactly as before.

Tests

Added to flashduty_test.go:

  • TestDoReportsIntermediaryOnNon2xxNonJSONBody — 504 + HTML body + empty request-id header → new message, contains the status/intermediary/timeout hint/body snippet, does not contain "malformed response".
  • TestDoReportsIntermediaryWithRequestID — same, but with a forwarded Flashcat-Request-Id header, to confirm the id is included when present.
  • TestDoMapsNon2xxJSONEnvelopeUnaffectedByGatewaySplit — 504 + valid JSON error envelope → still maps to *ErrorResponse unchanged.
  • Extended the existing TestDoExposesNonJSONBodyAsRaw doc comment to note it also guards the 2xx branch against this split.

Test plan

  • go build ./...
  • go vet ./...
  • go test -race ./...
  • make lint (golangci-lint, 0 issues)
  • make fmt (gofmt -s + gci) — no additional diffs beyond the change itself

A prod ALB timed out a long-running request and returned an HTML 504
page. doMethod reported it as "flashduty: malformed response (http
504, request_id ): invalid character '<' looking for beginning of
value", which reads like a Flashduty API JSON contract bug and led
both humans and AI agents to misdiagnose it. Split the non-JSON-body
failure by status class: a non-2xx response whose body isn't valid
envelope JSON never came from the Flashduty API (it always answers in
JSON), so name the intermediary, the missing/present request id, the
likely gateway/proxy timeout cause, a retry/split hint, and a body
snippet. 2xx non-JSON bodies (CSV/file exports) and non-2xx JSON error
envelopes are unaffected.
@ysyneu
ysyneu merged commit f275926 into main Jul 21, 2026
4 checks passed
@ysyneu
ysyneu deleted the fix/gateway-error-msg branch July 21, 2026 03:51
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.

1 participant