Description
samcli/local/lambda_service/local_lambda_http_service.py registers Flask routes with the default <string> URL converter:
"/2025-12-01/durable-executions/<durable_execution_arn>"
"/2025-12-01/durable-executions/<durable_execution_arn>/history"
"/2025-12-01/durable-executions/<durable_execution_arn>/stop"
"/2025-12-01/durable-execution-callbacks/<callback_id>/succeed"
"/2025-12-01/durable-execution-callbacks/<callback_id>/fail"
"/2025-12-01/durable-execution-callbacks/<callback_id>/heartbeat"
The <string> converter does not match /. After aws-durable-execution-sdk-python-testing#216, DurableExecutionArn values are minted as <uuid>/<invocation-id> and boto percent-encodes the / as %2F in the URI label. Werkzeug decodes %2F back to / during routing, so the path no longer matches a single <string> segment and the request 404s before reaching the handler:
botocore.exceptions.ClientError: An error occurred (PathNotFoundLocally) when calling
the GetDurableExecutionHistory operation: PathNotFoundException
The same shape applies to base64-encoded CallbackId values, which contain /.
Failing tests
local-start-lambda job in Integration Tests #8779:
tests/integration/local/start_lambda/test_start_lambda_durable.py::TestStartLambdaDurable::test_local_callback_cli_fail_{0,1,2}
…test_local_callback_cli_heartbeat
…test_local_start_lambda_invoke_durable_function_{HelloWorld,MapOperations,NamedStep,NamedWait,Parallel}
…test_local_start_lambda_invoke_timeout
…test_local_start_lambda_invoke_wait_for_callback_{success,failure}_{cli,http}*
All fail with PathNotFoundLocally on GetDurableExecutionHistory, GetDurableExecution, or StopDurableExecution. Reproduced locally on develop.
Fix
Use Werkzeug <path:...> converters for the ARN/callback URI labels. <path> accepts / and Werkzeug picks the right rule via trailing-literal specificity (verified with a Flask test client: …/<path:arn>/history wins over …/<path:arn> for …/abc%2Fdef/history). Existing unquote() calls in handlers can stay — idempotent on already-decoded values. Backwards-compatible with old UUID-only paths.
Description
samcli/local/lambda_service/local_lambda_http_service.pyregisters Flask routes with the default<string>URL converter:The
<string>converter does not match/. After aws-durable-execution-sdk-python-testing#216,DurableExecutionArnvalues are minted as<uuid>/<invocation-id>and boto percent-encodes the/as%2Fin the URI label. Werkzeug decodes%2Fback to/during routing, so the path no longer matches a single<string>segment and the request 404s before reaching the handler:The same shape applies to base64-encoded
CallbackIdvalues, which contain/.Failing tests
local-start-lambdajob in Integration Tests #8779:tests/integration/local/start_lambda/test_start_lambda_durable.py::TestStartLambdaDurable::test_local_callback_cli_fail_{0,1,2}…test_local_callback_cli_heartbeat…test_local_start_lambda_invoke_durable_function_{HelloWorld,MapOperations,NamedStep,NamedWait,Parallel}…test_local_start_lambda_invoke_timeout…test_local_start_lambda_invoke_wait_for_callback_{success,failure}_{cli,http}*All fail with
PathNotFoundLocallyonGetDurableExecutionHistory,GetDurableExecution, orStopDurableExecution. Reproduced locally ondevelop.Fix
Use Werkzeug
<path:...>converters for the ARN/callback URI labels.<path>accepts/and Werkzeug picks the right rule via trailing-literal specificity (verified with a Flask test client:…/<path:arn>/historywins over…/<path:arn>for…/abc%2Fdef/history). Existingunquote()calls in handlers can stay — idempotent on already-decoded values. Backwards-compatible with old UUID-only paths.