Generated client honors WithMaxRetries and retries once after a 401 refresh
Card: https://app.basecamp.com/2914079/buckets/48521764/card_tables/cards/10258105817
Surfaced by reviewers on #136 as pre-existing and spec-wide, so declined there.
WithMaxRetries was ignored for generated operations. The generated client runs its own retry loop and initGeneratedClient never passed WithRetryConfig, so all ~100 generated ops ran on the generated defaults (3 retries, 1s base delay) regardless of what the caller configured. MaxRetries and BaseDelay are now passed through; idempotency still decides whether a retry happens at all, so nothing changes for a non-idempotent op beyond the point below.
No 401 refresh path for generated operations. AuthManager refreshes 5 minutes ahead of expiry, but a mid-flight 401 was surfaced as-is where the hand-written Get/Post/form paths refresh and send once more. The generated client gains an AuthRefresher hook (WithAuthRefresher), wired to the same refreshCredentials the hand-written paths use. A 401 gets one refresh and one more send; a second 401 surfaces. Non-idempotent ops get this too, since a 401 means the server did nothing with the request.
To make the refresh path cover every op, non-idempotent operations now go through doWithRetry as well, keeping their single attempt. Bodies handed over as an io.Reader (*WithBody variants; three hand-written wrappers use them with bytes.NewReader) are rewound before each build, or held in memory when not seekable, so a resend carries the body the first send drained. That also closes a latent gap for idempotent *WithBody retries, which previously rebuilt from an exhausted reader.
Tests cover: retry count honored (0 and 2), 401 → refresh → retry once for a read and for a mutation, reader bodies sent whole both times, a second 401 surfaces after one refresh, and no retry when nothing can refresh. Each fails against main.
Not included: the code: 204 DELETE sweep mentioned on the card.
TMPDIR=/tmp/t make check green (MVP gate + conformance, 173/173).
Summary by cubic
Fixes the generated client ignoring WithMaxRetries and WithBaseDelay, so generated operations now honor the caller's retry settings instead of the default three retries. A mid-flight 401 now refreshes credentials once and resends the request, surfacing an error if the second attempt 401s again.
- Non-idempotent operations get this refresh too, since a 401 means the server took no action; all operations run through the same retry path.
- The resend shares the first pass's retry budget instead of starting fresh, and the delay ceiling follows the configured
BaseDelay. - Bodies passed as an
io.Readerare rewound before each send — buffered in memory when not seekable, kept from early closure when they are — so resends carry the full body. - Tests cover the retry count (0 and 2), refresh-then-resend for a read and a mutation, reader bodies sent whole twice, a second 401 surfacing, no retry when nothing can refresh, and the non-seekable in-memory body branch.