Skip to content

fix: Check the token response body read error where the body is load-bearing - #68

Open
keelerm84 wants to merge 2 commits into
mainfrom
mk/SDK-2822/auth-read-error
Open

fix: Check the token response body read error where the body is load-bearing#68
keelerm84 wants to merge 2 commits into
mainfrom
mk/SDK-2822/auth-read-error

Conversation

@keelerm84

@keelerm84 keelerm84 commented Aug 31, 2026

Copy link
Copy Markdown
Member

Summary

authorizeSalesforce captured the error from reading the token response body but did
not inspect it until after both status branches had already used those bytes -- the
error was passed to log.Print in each branch, which is how it surfaced without ever
being handled.

The naive fix, hoisting one check above the status branches, would be a regression. A
401 or 403 on a body read failure currently returns a permanent error, and hoisting
makes it non-permanent, leaving the daemon retrying forever against a credential
Salesforce has already rejected.

So the read error is judged per branch, according to whether the branch depends on
the bytes. The status line arrives ahead of the body, so net/http returns a
complete StatusCode whether or not the read finished; a truncated body casts no
doubt on the status.

  • 401 / 403 -- the status alone proves the credential was refused, and the body
    only feeds the log line. Stays permanent.
  • Other non-200 -- the body becomes the error message, so the read error is
    reported here.
  • 200 -- the body is the token document, so the read error is returned before
    json.Unmarshal.

Two further fixes in the same lines. The generic non-200 branch returned
errors.New(string(errorBody)), which on an empty body is a non-nil error that
prints nothing -- a bodyless 503 from a load balancer in front of Salesforce is
ordinary, and the existing 503 is retried test case passed only because it asserted
err != nil. The message now always carries the status code, on both the read-failure
path and the normal one. The local was also renamed to readErr, since which error is
in hand is the whole bug.

The new tests fix one grant, since every line under test runs after client.Do and
existing coverage already establishes that response handling is grant-independent.
They were verified to discriminate in both directions: against the original code, and
against the naive hoist, which fails the 401 and 403 rows on the permanent flag.


Note

Overview
authorizeSalesforce now treats a truncated token response body differently depending on HTTP status, instead of only logging the read error while still using partial bytes.

For 401/403, the failure stays permanent and returns Salesforce Unauthorized even when the body read fails—so bad credentials are not retried forever. For other non-200 responses, a body read failure is returned (wrapped with the status code); otherwise the error message always includes the status and body text, fixing empty errors on bodyless responses like 503. For 200, a partial body read is returned before JSON parsing so a truncated token is never stored.

Adds authorize_readerr_test.go with a truncating mock token server and tests that pin the permanent vs retry behavior and non-empty transient error messages (including guarding against a naive “check readErr first” regression).

Reviewed by Cursor Bugbot for commit 099edf9. Bugbot is set up for automated code reviews on this repo. Configure here.

…bearing

authorizeSalesforce read the token response body and then ignored the read
error until after both status branches had already used the bytes. The error
was only ever logged, which is why it never surfaced as a defect.

The obvious fix -- hoist "if err != nil { return err, false }" above the status
branches -- is wrong, and this commit deliberately does not do it. A 401 or 403
whose body read fails would then return a non-permanent error, so the daemon
would retry forever against a credential Salesforce has already rejected. That
trades a cosmetically tidy check for the exact failure the permanent flag exists
to prevent.

The status line arrives ahead of the body, so net/http delivers a complete
StatusCode whether or not the body read finished. A truncated body therefore puts
nothing about the status code in doubt, and each branch can judge the read error
on whether it actually depends on the body:

- 401 and 403: the status code alone proves the credential is rejected, and the
  body only feeds the log line. The classification stays permanent.
- Any other non-200: the body becomes the error message, so a failed read is
  returned instead of a half-received message standing in for the failure.
- 200: the body is the token document, so a failed read is returned rather than
  a partial document being parsed for a token.

The generic non-200 branch also returned errors.New on the body alone, which
prints nothing when the body is empty -- and a 503 with no body from a load
balancer in front of Salesforce is ordinary. The status code now goes in the
message either way, so the caller always has something to log.

Tests cover the read failure against each status class that is treated
differently, asserting the returned error and the permanent flag. Verified
discriminating: the 401 and 403 rows fail against the naive hoist, and the 500
row plus both message rows fail against the code before this change.
The non-200 branch returned the read error bare while the branch beside it built a
message carrying the status code. Both paths report the same failure to the same
caller, which only logs it, and "unexpected EOF" alone does not say what failed.

Wrapped with %w rather than concatenated, so errors.Is still reaches the
underlying read error.
@keelerm84
keelerm84 requested a review from a team as a code owner August 31, 2026 15:08
@keelerm84
keelerm84 requested a review from a team August 31, 2026 17:31
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