Botocore sync needs your input
Botocore 1.43.67 introduces changes that require a port before the upper bound can be raised.
ENABLE_BUMP is false for the sync bot, so this run made no code changes — it only analyzed
the diff and validated the hashes.
Botocore diff: boto/botocore@1.43.62...1.43.67
Sync PR: none open (last one, #1693, is merged and covers up to < 1.43.63)
WIP PR: none
Classifier verdict: port-required
register_retry_handler (tracked override) gains MaxAttemptsSeeder registration; aiobotocore
override must mirror it
| File |
Function |
Change |
Verdict |
Reason |
| botocore/init.py |
module data (__version__, _xform_cache) |
changed |
pure-sync |
Version bump + new static dict entries (WhatsApp ops); no function logic changed |
| botocore/retries/standard.py |
register_retry_handler |
changed |
port-required |
Exact match in overrides (standard.register_retry_handler); adds new client.meta.events.register(...) call |
| botocore/retries/standard.py |
MaxAttemptsSeeder (new class) |
added |
port-required |
New class used inside the now-changed register_retry_handler; aiobotocore's reimplementation lacks it |
Hash check confirms it — tests/test_patches.py fails exactly where the classifier predicted:
FAILED tests/test_patches.py::test_patches[register_retry_handler-digests211]
assert 'dc2a6d66286b5413704564fe21e421343a323136' not in the recorded digests
1 failed, 212 passed
What changed upstream
MaxAttemptsSeeder was added in botocore 1.43.66 (absent in 1.43.65). It seeds
request.context['retries']['max'] at request-created time so the max token of the
amz-sdk-request header is present on the initial attempt, not just from the first retry
onward. register_retry_handler now registers it inside the NEW_RETRIES_ENABLED branch:
client.meta.events.register(
f'request-created.{service_event_name}',
MaxAttemptsSeeder(max_attempts).seed_max_attempts,
unique_id=f'seed-max-attempts-{service_event_name}',
)
aiobotocore/retries/standard.py reimplements register_retry_handler wholesale (it must pick
AioRetryHandler vs AnyioRetryHandler and the Aio* policy classes), so it does not inherit
this registration. Without the port, aiobotocore emits amz-sdk-request: attempt=1 with no max
token on the first attempt — a behavioral divergence from botocore, not just a stale hash.
MaxAttemptsSeeder.seed_max_attempts is a plain sync handler that only mutates
request.context — no I/O — so it needs no async adaptation. The adaptive path is unaffected:
aiobotocore/retries/adaptive.py only registers the rate limiter and gets the seeder via the
standard handler, same as botocore.
Questions
1. Confirm the port shape: import MaxAttemptsSeeder from botocore rather than reimplementing it?
- Context: the class is sync-only and has no awaitables in it. aiobotocore's file already
imports 18 names from botocore.retries.standard (ExponentialBackoff, MaxAttemptsChecker,
RetryQuotaChecker, …) and only subclasses what genuinely needs async.
- Option A (preferred): add
MaxAttemptsSeeder to the existing import list and mirror the
registration block verbatim into aiobotocore's register_retry_handler, same position, same
unique_id. Minimal diff from botocore, per CLAUDE.md §"Minimize divergence".
- Option B: reimplement the class locally. No upside — it would be pure drift.
- Trade-off: A couples us to a botocore symbol that only exists from 1.43.66 — see question 2.
2. Does this port raise the lower bound to 1.43.66/1.43.67, or should the import be guarded?
- Context: current bound is
botocore >= 1.43.3, < 1.43.63. A bare
from botocore.retries.standard import MaxAttemptsSeeder is an ImportError on every supported
botocore below 1.43.66 — i.e. the entire currently-supported range.
- Option A: let
/aiobotocore-bot:update-botocore-bounds --mode=port move both bounds to
>= 1.43.67, < 1.43.68. This is what the skill does in port mode and matches how aiobotocore has
historically handled ports. Clean, no conditional code.
- Option B: keep the wide lower bound and guard with
try: ... except ImportError: /
hasattr so the seeder is registered only when available.
- Trade-off: A is a hard floor bump for downstream users on a narrow botocore pin; B preserves
the range but adds version-conditional branching that has to be carried until the floor moves
anyway — and it is exactly the kind of unmatched divergence CLAUDE.md asks us to avoid.
My reading is A, but it is a user-visible packaging decision, so I would rather you confirm.
3. How far should the ported tests go — the mirror lacks BaseRetryTest?
- Context: botocore added
TestMaxAttemptsInRequestHeader (4 tests) to
tests/unit/retries/test_standard_retry_v2_1.py, which does have an aiobotocore mirror at
tests/botocore_tests/unit/retries/test_standard_retry_v2_1.py. But the new class subclasses
tests.functional.test_retry.BaseRetryTest, and there is no tests/botocore_tests/functional/test_retry.py
mirror — so the base class does not exist on our side. ClientHTTPStubber does exist
(tests/botocore_tests/__init__.py:183).
- Option A: port only
test_seeder_adds_max_to_empty_context (the pure-unit one, no base class
needed) plus a small async test that builds a client and asserts the max token on the first
amz-sdk-request header, using the existing ClientHTTPStubber. Targeted, no new harness.
- Option B: port
BaseRetryTest into tests/botocore_tests/functional/test_retry.py first, so
all four upstream tests port near-verbatim and future retry-test syncs have the base available.
- Trade-off: B is more upfront work and pulls in a functional-test harness we have so far done
without; A is cheaper but leaves the next retry sync to face the same gap. Leaning A unless you
want the harness backfilled now.
Also in the diff (no action needed)
botocore/__init__.py moved __version__ and gained _xform_cache entries for new WhatsApp
operations — static data, no function logic. tests/unit/test_utils.py and the endpoint-rules
JSON fixtures changed but have no aiobotocore mirror.
Scope estimate
Small. One import line, one ~10-line registration block, one hash update in
tests/test_patches.py (add dc2a6d66286b5413704564fe21e421343a323136), plus tests and the bound
update. The only genuinely open items are questions 2 and 3.
How to respond
Reply with your answers. You can:
- Answer questions directly
- Ask
@claude for more context (e.g. "@claude show me the botocore diff for question 2")
- Provide partial answers — the bot will proceed with what it can and ask follow-ups
- Suggest a different approach entirely
- Flip
ENABLE_BUMP to true if you want the bot to attempt the port itself next run
Once resolved, the bot will apply decisions on its next run. Close this issue when done.
Botocore sync needs your input
Botocore 1.43.67 introduces changes that require a port before the upper bound can be raised.
ENABLE_BUMPisfalsefor the sync bot, so this run made no code changes — it only analyzedthe diff and validated the hashes.
Botocore diff: boto/botocore@1.43.62...1.43.67
Sync PR: none open (last one, #1693, is merged and covers up to
< 1.43.63)WIP PR: none
Classifier verdict:
port-required__version__,_xform_cache)register_retry_handlerstandard.register_retry_handler); adds newclient.meta.events.register(...)callMaxAttemptsSeeder(new class)register_retry_handler; aiobotocore's reimplementation lacks itHash check confirms it —
tests/test_patches.pyfails exactly where the classifier predicted:What changed upstream
MaxAttemptsSeederwas added in botocore 1.43.66 (absent in 1.43.65). It seedsrequest.context['retries']['max']atrequest-createdtime so themaxtoken of theamz-sdk-requestheader is present on the initial attempt, not just from the first retryonward.
register_retry_handlernow registers it inside theNEW_RETRIES_ENABLEDbranch:aiobotocore/retries/standard.pyreimplementsregister_retry_handlerwholesale (it must pickAioRetryHandlervsAnyioRetryHandlerand theAio*policy classes), so it does not inheritthis registration. Without the port, aiobotocore emits
amz-sdk-request: attempt=1with nomaxtoken on the first attempt — a behavioral divergence from botocore, not just a stale hash.
MaxAttemptsSeeder.seed_max_attemptsis a plain sync handler that only mutatesrequest.context— no I/O — so it needs no async adaptation. The adaptive path is unaffected:aiobotocore/retries/adaptive.pyonly registers the rate limiter and gets the seeder via thestandard handler, same as botocore.
Questions
1. Confirm the port shape: import
MaxAttemptsSeederfrom botocore rather than reimplementing it?imports 18 names from
botocore.retries.standard(ExponentialBackoff,MaxAttemptsChecker,RetryQuotaChecker, …) and only subclasses what genuinely needs async.MaxAttemptsSeederto the existing import list and mirror theregistration block verbatim into aiobotocore's
register_retry_handler, same position, sameunique_id. Minimal diff from botocore, per CLAUDE.md §"Minimize divergence".2. Does this port raise the lower bound to 1.43.66/1.43.67, or should the import be guarded?
botocore >= 1.43.3, < 1.43.63. A barefrom botocore.retries.standard import MaxAttemptsSeederis anImportErroron every supportedbotocore below 1.43.66 — i.e. the entire currently-supported range.
/aiobotocore-bot:update-botocore-bounds --mode=portmove both bounds to>= 1.43.67, < 1.43.68. This is what the skill does in port mode and matches how aiobotocore hashistorically handled ports. Clean, no conditional code.
try: ... except ImportError:/hasattrso the seeder is registered only when available.the range but adds version-conditional branching that has to be carried until the floor moves
anyway — and it is exactly the kind of unmatched divergence CLAUDE.md asks us to avoid.
My reading is A, but it is a user-visible packaging decision, so I would rather you confirm.
3. How far should the ported tests go — the mirror lacks
BaseRetryTest?TestMaxAttemptsInRequestHeader(4 tests) totests/unit/retries/test_standard_retry_v2_1.py, which does have an aiobotocore mirror attests/botocore_tests/unit/retries/test_standard_retry_v2_1.py. But the new class subclassestests.functional.test_retry.BaseRetryTest, and there is notests/botocore_tests/functional/test_retry.pymirror — so the base class does not exist on our side.
ClientHTTPStubberdoes exist(
tests/botocore_tests/__init__.py:183).test_seeder_adds_max_to_empty_context(the pure-unit one, no base classneeded) plus a small async test that builds a client and asserts the
maxtoken on the firstamz-sdk-requestheader, using the existingClientHTTPStubber. Targeted, no new harness.BaseRetryTestintotests/botocore_tests/functional/test_retry.pyfirst, soall four upstream tests port near-verbatim and future retry-test syncs have the base available.
without; A is cheaper but leaves the next retry sync to face the same gap. Leaning A unless you
want the harness backfilled now.
Also in the diff (no action needed)
botocore/__init__.pymoved__version__and gained_xform_cacheentries for new WhatsAppoperations — static data, no function logic.
tests/unit/test_utils.pyand the endpoint-rulesJSON fixtures changed but have no aiobotocore mirror.
Scope estimate
Small. One import line, one ~10-line registration block, one hash update in
tests/test_patches.py(adddc2a6d66286b5413704564fe21e421343a323136), plus tests and the boundupdate. The only genuinely open items are questions 2 and 3.
How to respond
Reply with your answers. You can:
@claudefor more context (e.g. "@claude show me the botocore diff for question 2")ENABLE_BUMPtotrueif you want the bot to attempt the port itself next runOnce resolved, the bot will apply decisions on its next run. Close this issue when done.