Shopify / Shopify/shopify-app-python

Retry-After is never read on 429 retries: dict(httpx.Headers) lowercases the key

Open Beginner friendly
#17 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

devtools-gardener
Dominant language
Python
Stars
16
Forks
1
Avg merge
3m
Merged PRs (30d)
2

Description

Summary

In admin_graphql_request, the Retry-After header is never read on a 429 response. The lookup always falls through to its "1" default, so every rate-limit retry sleeps exactly one second regardless of what the server asked for.

Affects both the sync and async paths.

Version: shopifyapp 1.0.1 (sdist from PyPI), httpx 0.28.1.

Cause

shopify_app/graphql/admin_graphql.py:111 (and :708 on the async path) normalizes the response headers with:

response_headers = dict(response.headers)

dict() on an httpx.Headers instance produces lowercased keys. The 429 handlers then look the header up with its canonical casing, at shopify_app/graphql/admin_graphql.py:389 (sync) and :445 (async):

retry_after = response_headers.get("Retry-After", "1")

That key is never present, so retry_after is always the literal string "1".

Reproduction

import httpx

h = httpx.Headers({"Retry-After": "2.0", "Content-Type": "application/json"})
d = dict(h)

print(list(d))                     # ['retry-after', 'content-type']
print(d.get("Retry-After", "1"))   # '1'   <- expected '2.0'

Impact

The client ignores server-provided backoff on rate limiting and retries on a fixed one-second interval instead. With the default max_retries=2 that is about two seconds of total backoff, typically well short of what a rate-limited endpoint asks for.

Suggested fix

httpx.Headers is already case-insensitive, so reading from the response object directly avoids the problem:

retry_after = response.headers.get("Retry-After", "1")

Keeping the dict and looking up the lowercase key works too.

One caveat: fixing this lookup on its own exposes a second defect on the same value, where int(retry_after) raises ValueError on anything that is not a bare integer. Filed separately as #18, which also notes that the two are best addressed together.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with shopify_app/graphql/admin_graphql.py at lines 111 and 708, then inspect the sync and async 429 handlers at lines 389 and 445. Reproduce the header-key behavior with the provided httpx snippet, and confirm both retry paths use the server-provided Retry-After value rather than the default.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
api, backend
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
78/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.