Repository navigation
Bug: idempotent functions return None on replay when the stored response is falsy (0, False, "", [], {}) #8700
Description
Expected Behaviour
When an idempotent function returns a falsy value (0, False, "", [], {}), a retry with the same payload should return that same value from the persistence store.
Current Behaviour
The first call returns the value and it is stored correctly, but every replay returns None.
IdempotencyHandler._handle_for_status reads the stored response with:
serialized_response = self.output_serializer.from_dict(response_dict) if response_dict else Noneso any falsy response_dict becomes None. #8090 pointed out this line together with the write path in _get_function_response. The write path was changed to is not None, but the read path still uses a truthiness check, so the value is saved and then dropped on replay.
Code snippet
from aws_lambda_powertools.utilities.idempotency import idempotent_function
from aws_lambda_powertools.utilities.idempotency.exceptions import (
IdempotencyItemAlreadyExistsError,
IdempotencyItemNotFoundError,
)
from aws_lambda_powertools.utilities.idempotency.persistence.base import BasePersistenceLayer, DataRecord
class InMemoryPersistenceLayer(BasePersistenceLayer):
def __init__(self):
super().__init__()
self.records = {}
def _get_record(self, idempotency_key) -> DataRecord:
if idempotency_key not in self.records:
raise IdempotencyItemNotFoundError
return self.records[idempotency_key]
def _put_record(self, data_record: DataRecord) -> None:
if data_record.idempotency_key in self.records:
raise IdempotencyItemAlreadyExistsError
self.records[data_record.idempotency_key] = data_record
def _update_record(self, data_record: DataRecord) -> None:
self.records[data_record.idempotency_key] = data_record
def _delete_record(self, data_record: DataRecord) -> None:
self.records.pop(data_record.idempotency_key, None)
persistence_layer = InMemoryPersistenceLayer()
@idempotent_function(data_keyword_argument="order", persistence_store=persistence_layer)
def count_items(order):
return len(order["items"])
order = {"order_id": "o-1", "items": []}
print(count_items(order=order)) # 0
print(count_items(order=order)) # None, expected 0I see the same with CachePersistenceLayer on a real Redis server: 0, False, "", [] and {} all come back as None on replay, while None and truthy values are returned correctly.
Possible Solution
Use is not None, matching the write path:
serialized_response = self.output_serializer.from_dict(response_dict) if response_dict is not None else NoneDataRecord.response_json_as_dict() already returns None when there is no stored data, and a function that returns None is stored as null, so that case keeps returning None.
Steps to Reproduce
Run the snippet above. Any persistence layer is affected, since the check is in the shared handler.
Powertools for AWS Lambda (Python) version
latest (3.37.0, also on develop)
AWS Lambda function runtime
3.13
Packaging format used
PyPi
Debugging logs
N/A, the replay returns None without an error
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsTriage
Activity
thebadalov commentedon Oct 29, 2026
I'd like to work on this one. I have the one-line fix and a regression test ready, and I'll open the PR right away so it's ready for review. Could you assign it to me?
leandrodamascena commentedon Oct 29, 2026
Thanks @thebadalov, confirmed: the first call returns
0, the replay returnsNone. This was item 2 on my Idempotency list in #8603, the truthiness check on the read side that #8176 left behind. It's yours, and I see #8701 is already up, I'll review it there. This goes into 3.40.0.thebadalov commentedon Oct 29, 2026
Thanks @leandrodamascena! Happy to change anything in #8701 if needed.