Skip to content

A 401 token-refresh replay is not counted against the attempt cap #565

Description

@jeremy

Python (_http.py / _async_http.py) and Ruby (http.rb) replay a request once after a 401 when the token provider is refreshable. The replay lives inside _single_request / single_request and is governed by its own _retry_count / retry_count counter, entirely separate from the caller-facing attempt budget.

Consequence: with max_retries: 0 — which every SDK documents as "exactly one attempt" — a 401 whose refresh succeeds still puts two requests on the wire. SPEC §14 makes the same promise for downloads ("disabling retry yields exactly ONE hop-1 attempt"), so the tension is now written down in two places.

This predates #563. The replay is in the single-request primitive, which mains get_no_retry already called, so routing the download hop through the retry loop did not change it — surfaced by review on #563 (python/src/basecamp/_async_http.py).

The real question is definitional, and worth settling before patching: is a credential-refresh replay a retry? Two defensible readings.

  • It is. Then it must draw from the same budget, and max_retries: 0 means one request no matter what — refresh included. Simple to state, simple to test, and makes the §14 sentence literally true.
  • It is not. A refresh replay is re-authentication, not a retry of a failure: the first request never reached a state the caller would call an attempt. Then the cap governs retries only, and the docs plus §14 should say "one hop-1 retry attempt" rather than implying a request count.

Whichever we pick, the fix spans Python and Ruby (Go, TypeScript, Kotlin and Swift have no 401-refresh replay in the transport at all — which is its own parity question worth answering in the same pass).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions