Describe the bug:
Waiter.wait() does not validate Delay and MaxAttempts values in WaiterConfig. Unlike API parameters (validated by ParamValidator and the AWS server), WaiterConfig is popped from kwargs and consumed locally with no validation at any layer.
This leads to Python runtime errors or silently broken behavior:
Delay: -1 → ValueError: sleep length must be non-negative
Delay: 0 → No sleep between polls, rapid-fire API calls
MaxAttempts: 0 or -1 → Polls once, then immediately raises WaiterError
MaxAttempts: 'abc' → TypeError: '>=' not supported between instances of 'int' and 'str'
Other input types are validated properly:
| Parameter type |
Invalid input |
Validated by |
API params (MaxResults: -1) |
InvalidParameterValue |
AWS API server |
Type mismatch (InstanceIds: [-1]) |
ParamValidationError |
botocore ParamValidator |
CLI common (--generate-cli-skeleton invalid) |
ParamValidation |
argparse |
Pagination (--max-items -1) |
Warning printed |
CLI (warning only) |
WaiterConfig (Delay: -1) |
ValueError from time.sleep() |
None |
Expected Behavior:
Waiter.wait() should raise a clear error when WaiterConfig contains invalid values, before the polling loop begins.
Current Behavior:
# Delay: -1 → Python runtime error
>>> waiter.wait(Bucket='my-bucket', WaiterConfig={'Delay': -1, 'MaxAttempts': 2})
ValueError: sleep length must be non-negative
# MaxAttempts: 'abc' → Python runtime error
>>> waiter.wait(Bucket='my-bucket', WaiterConfig={'MaxAttempts': 'abc'})
TypeError: '>=' not supported between instances of 'int' and 'str'
# Delay: 0 → no error, but no sleep between API calls
>>> waiter.wait(Bucket='my-bucket', WaiterConfig={'Delay': 0, 'MaxAttempts': 2})
# Two API calls fired back-to-back with no interval
# MaxAttempts: 0 → polls once, then fails immediately regardless of state
>>> waiter.wait(Bucket='my-bucket', WaiterConfig={'MaxAttempts': 0})
WaiterError: Waiter BucketExists failed: Max attempts exceeded
The relevant code in botocore/waiter.py:
config = kwargs.pop('WaiterConfig', {})
sleep_amount = config.get('Delay', self.config.delay)
max_attempts = config.get('MaxAttempts', self.config.max_attempts)
# No validation — values go directly into time.sleep() and >= comparison
Reproduction Steps:
import boto3
s3 = boto3.client('s3')
waiter = s3.get_waiter('bucket_exists')
# Case 1: Delay -1 → ValueError from time.sleep()
waiter.wait(Bucket='any-bucket', WaiterConfig={'Delay': -1, 'MaxAttempts': 2})
# Case 2: MaxAttempts 'abc' → TypeError from >= comparison
waiter.wait(Bucket='any-bucket', WaiterConfig={'Delay': 1, 'MaxAttempts': 'abc'})
# Case 3: Delay 0 → rapid-fire API calls (no error, but dangerous)
waiter.wait(Bucket='any-bucket', WaiterConfig={'Delay': 0, 'MaxAttempts': 3})
Possible Solution:
Add validation at the top of Waiter.wait(), matching the waiter JSON schema constraints already defined in tests/functional/test_waiter_config.py (delay >= 0, maxAttempts >= 1):
config = kwargs.pop('WaiterConfig', {})
sleep_amount = config.get('Delay', self.config.delay)
max_attempts = config.get('MaxAttempts', self.config.max_attempts)
if not isinstance(sleep_amount, (int, float)) or sleep_amount < 0:
raise WaiterConfigError(
error_msg=f'Invalid value for Delay: {sleep_amount!r}. Must be a non-negative number.'
)
if not isinstance(max_attempts, int) or max_attempts < 1:
raise WaiterConfigError(
error_msg=f'Invalid value for MaxAttempts: {max_attempts!r}. Must be a positive integer.'
)
Additional Information/Context:
This also affects AWS CLI, which recently added --delay and --max-attempts flags for wait commands (aws/aws-cli#10224). I've added CLI-layer validation there, but boto3 users calling waiter.wait() directly are still exposed.
SDK version used: botocore 1.35.99
Environment details: macOS Darwin 25.4.0, Python 3.12.2
Describe the bug:
Waiter.wait()does not validateDelayandMaxAttemptsvalues inWaiterConfig. Unlike API parameters (validated byParamValidatorand the AWS server),WaiterConfigis popped from kwargs and consumed locally with no validation at any layer.This leads to Python runtime errors or silently broken behavior:
Delay: -1→ValueError: sleep length must be non-negativeDelay: 0→ No sleep between polls, rapid-fire API callsMaxAttempts: 0or-1→ Polls once, then immediately raisesWaiterErrorMaxAttempts: 'abc'→TypeError: '>=' not supported between instances of 'int' and 'str'Other input types are validated properly:
MaxResults: -1)InvalidParameterValueInstanceIds: [-1])ParamValidationErrorParamValidator--generate-cli-skeleton invalid)ParamValidation--max-items -1)Delay: -1)ValueErrorfromtime.sleep()Expected Behavior:
Waiter.wait()should raise a clear error whenWaiterConfigcontains invalid values, before the polling loop begins.Current Behavior:
The relevant code in
botocore/waiter.py:Reproduction Steps:
Possible Solution:
Add validation at the top of
Waiter.wait(), matching the waiter JSON schema constraints already defined intests/functional/test_waiter_config.py(delay >= 0,maxAttempts >= 1):Additional Information/Context:
This also affects AWS CLI, which recently added
--delayand--max-attemptsflags forwaitcommands (aws/aws-cli#10224). I've added CLI-layer validation there, but boto3 users callingwaiter.wait()directly are still exposed.SDK version used: botocore 1.35.99
Environment details: macOS Darwin 25.4.0, Python 3.12.2