From cf53cef2e5b402fe5aa5dcd3858661dcb03694c8 Mon Sep 17 00:00:00 2001 From: vyrnsynx <153433026+vyrnsynx@users.noreply.github.com> Date: Sun, 13 Sep 2026 00:13:52 +0000 Subject: [PATCH 1/2] fix: match invalid checkpoint token casing Service emits "Invalid checkpoint token" (lowercase). The SDK compared with a Title Case prefix, so stale tokens were treated as non-retryable execution failures. Compare case-insensitively and cover both casings in unit tests. Fixes #721 --- .../aws_durable_execution_sdk_python/exceptions.py | 10 +++++----- .../tests/exceptions_test.py | 13 ++++++++++--- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/packages/aws-durable-execution-sdk-python/src/aws_durable_execution_sdk_python/exceptions.py b/packages/aws-durable-execution-sdk-python/src/aws_durable_execution_sdk_python/exceptions.py index d51ab961..40963c09 100644 --- a/packages/aws-durable-execution-sdk-python/src/aws_durable_execution_sdk_python/exceptions.py +++ b/packages/aws-durable-execution-sdk-python/src/aws_durable_execution_sdk_python/exceptions.py @@ -13,7 +13,7 @@ TOO_MANY_REQUESTS_ERROR: int = 429 SERVICE_ERROR: int = 500 INVALID_PARAMETER_VALUE_EXCEPTION: str = "InvalidParameterValueException" -INVALID_CHECKPOINT_TOKEN_PREFIX: str = "Invalid Checkpoint Token" +INVALID_CHECKPOINT_TOKEN_PREFIX: str = "invalid checkpoint token" # Non-retryable customer error codes that arrive as non-4xx (e.g. HTTP 502) from Lambda. # Unlike typical 5xx errors, these require customer intervention (e.g., fixing @@ -162,7 +162,7 @@ def _classify_error_category( These arrive as HTTP 502 but require customer intervention to fix. - 4xx errors → EXECUTION, except: - 429 (TooManyRequests) → INVOCATION (throttling is transient) - - InvalidParameterValueException with "Invalid Checkpoint Token" → INVOCATION + - InvalidParameterValueException with "Invalid checkpoint token" (case-insensitive) → INVOCATION (stale token from a concurrent checkpoint; next invocation gets a fresh token) - 5xx, network errors → INVOCATION """ @@ -180,9 +180,9 @@ def _classify_error_category( and error and not ( (error.get("Code") or "") == INVALID_PARAMETER_VALUE_EXCEPTION - and (error.get("Message") or "").startswith( - INVALID_CHECKPOINT_TOKEN_PREFIX - ) + and (error.get("Message") or "") + .casefold() + .startswith(INVALID_CHECKPOINT_TOKEN_PREFIX) ) ): return DurableApiErrorCategory.EXECUTION diff --git a/packages/aws-durable-execution-sdk-python/tests/exceptions_test.py b/packages/aws-durable-execution-sdk-python/tests/exceptions_test.py index 3ae6b1fb..b0b6a840 100644 --- a/packages/aws-durable-execution-sdk-python/tests/exceptions_test.py +++ b/packages/aws-durable-execution-sdk-python/tests/exceptions_test.py @@ -65,12 +65,19 @@ def test_checkpoint_error(): assert error.termination_reason == TerminationReason.CHECKPOINT_FAILED -def test_checkpoint_error_classification_invalid_token_invocation(): - """Test 4xx InvalidParameterValueException with Invalid Checkpoint Token is invocation error.""" +@pytest.mark.parametrize( + "message", + [ + "Invalid checkpoint token: token expired", + "Invalid Checkpoint Token: token expired", + ], +) +def test_checkpoint_error_classification_invalid_token_invocation(message: str): + """Service emits lowercase 'checkpoint token'; match case-insensitively as invocation.""" error_response = { "Error": { "Code": "InvalidParameterValueException", - "Message": "Invalid Checkpoint Token: token expired", + "Message": message, }, "ResponseMetadata": {"HTTPStatusCode": 400}, } From 3025b34e1d0e32810b68af0a4314d5d665d796e2 Mon Sep 17 00:00:00 2001 From: vyrnsynx <153433026+vyrnsynx@users.noreply.github.com> Date: Tue, 15 Sep 2026 10:14:59 +0000 Subject: [PATCH 2/2] fix: use exact Invalid checkpoint token match Match the backend error prefix case-sensitively and restore the single-unit test description preferred in review. --- .../aws_durable_execution_sdk_python/exceptions.py | 10 +++++----- .../tests/exceptions_test.py | 13 +++---------- 2 files changed, 8 insertions(+), 15 deletions(-) diff --git a/packages/aws-durable-execution-sdk-python/src/aws_durable_execution_sdk_python/exceptions.py b/packages/aws-durable-execution-sdk-python/src/aws_durable_execution_sdk_python/exceptions.py index 40963c09..ea3f163f 100644 --- a/packages/aws-durable-execution-sdk-python/src/aws_durable_execution_sdk_python/exceptions.py +++ b/packages/aws-durable-execution-sdk-python/src/aws_durable_execution_sdk_python/exceptions.py @@ -13,7 +13,7 @@ TOO_MANY_REQUESTS_ERROR: int = 429 SERVICE_ERROR: int = 500 INVALID_PARAMETER_VALUE_EXCEPTION: str = "InvalidParameterValueException" -INVALID_CHECKPOINT_TOKEN_PREFIX: str = "invalid checkpoint token" +INVALID_CHECKPOINT_TOKEN_PREFIX: str = "Invalid checkpoint token" # Non-retryable customer error codes that arrive as non-4xx (e.g. HTTP 502) from Lambda. # Unlike typical 5xx errors, these require customer intervention (e.g., fixing @@ -162,7 +162,7 @@ def _classify_error_category( These arrive as HTTP 502 but require customer intervention to fix. - 4xx errors → EXECUTION, except: - 429 (TooManyRequests) → INVOCATION (throttling is transient) - - InvalidParameterValueException with "Invalid checkpoint token" (case-insensitive) → INVOCATION + - InvalidParameterValueException with "Invalid checkpoint token" (exact match) → INVOCATION (stale token from a concurrent checkpoint; next invocation gets a fresh token) - 5xx, network errors → INVOCATION """ @@ -180,9 +180,9 @@ def _classify_error_category( and error and not ( (error.get("Code") or "") == INVALID_PARAMETER_VALUE_EXCEPTION - and (error.get("Message") or "") - .casefold() - .startswith(INVALID_CHECKPOINT_TOKEN_PREFIX) + and (error.get("Message") or "").startswith( + INVALID_CHECKPOINT_TOKEN_PREFIX + ) ) ): return DurableApiErrorCategory.EXECUTION diff --git a/packages/aws-durable-execution-sdk-python/tests/exceptions_test.py b/packages/aws-durable-execution-sdk-python/tests/exceptions_test.py index b0b6a840..540f09fa 100644 --- a/packages/aws-durable-execution-sdk-python/tests/exceptions_test.py +++ b/packages/aws-durable-execution-sdk-python/tests/exceptions_test.py @@ -65,19 +65,12 @@ def test_checkpoint_error(): assert error.termination_reason == TerminationReason.CHECKPOINT_FAILED -@pytest.mark.parametrize( - "message", - [ - "Invalid checkpoint token: token expired", - "Invalid Checkpoint Token: token expired", - ], -) -def test_checkpoint_error_classification_invalid_token_invocation(message: str): - """Service emits lowercase 'checkpoint token'; match case-insensitively as invocation.""" +def test_checkpoint_error_classification_invalid_token_invocation(): + """Test 4xx InvalidParameterValueException with "Invalid checkpoint token" error message is an invocation error.""" error_response = { "Error": { "Code": "InvalidParameterValueException", - "Message": message, + "Message": "Invalid checkpoint token: token expired", }, "ResponseMetadata": {"HTTPStatusCode": 400}, }