Repository navigation
[Bug]: WebServer route layer doesn't URL-decode path segments #222
Description
Summary
dex-local-runner (WebRunner) returns 404 for every Get/State/History/Checkpoint/Stop call against an execution whose ARN contains a literal /, and silently returns an empty list for ListDurableExecutionsByFunction when the function name contains : or $. The cause is that the WebServer's route layer does not URL-decode captured path segments before using them as store-lookup keys, while boto's rest-json serializer percent-encodes those characters in non-greedy URI labels.
Affected versions
aws-durable-execution-sdk-python-testing == 1.2.0 (PyPI). The bug was introduced in #216 (which started minting ARNs of the form <uuid>/<invocation-id>) and shipped in v1.2.0.
Affected runners
WebRunner/dex-local-runner— broken for every nontrivial durable function, since the firstcontext.step/context.wait/context.callbacktriggers a checkpoint call that 404s.DurableFunctionTestRunner(in-process) — not affected; usesInMemoryServiceClientwhich ignoresdurable_execution_arn.DurableFunctionCloudTestRunner(real AWS) — not affected; ARNs come from the real Lambda backend and never round-trip through the local route layer.
Affected operations
All five durable-execution operations whose URI label is the ARN, plus the by-function list:
| Operation | URI template |
|---|---|
GetDurableExecution |
GET /2025-12-01/durable-executions/{DurableExecutionArn} |
GetDurableExecutionState |
GET /2025-12-01/durable-executions/{DurableExecutionArn}/state |
GetDurableExecutionHistory |
GET /2025-12-01/durable-executions/{DurableExecutionArn}/history |
CheckpointDurableExecution |
POST /2025-12-01/durable-executions/{DurableExecutionArn}/checkpoint |
StopDurableExecution |
POST /2025-12-01/durable-executions/{DurableExecutionArn}/stop |
ListDurableExecutionsByFunction |
GET /2025-12-01/functions/{FunctionName}/durable-executions |
For non-greedy labels (no +), boto encodes:
/→%2F:→%3A$→%24
Reproduction
import boto3
from aws_durable_execution_sdk_python_testing import WebRunner, WebRunnerConfig
from aws_durable_execution_sdk_python_testing.web.server import WebServiceConfig
with WebRunner(WebRunnerConfig(web_service=WebServiceConfig(port=0))) as runner:
# ...start an execution via runner so a slash-containing ARN is minted...
arn = "<uuid>/<invocation-id>" # the format Execution.new() produces
client = boto3.client("lambda", endpoint_url="http://127.0.0.1:<port>", ...)
client.get_durable_execution(DurableExecutionArn=arn)
# botocore.exceptions.ClientError:
# An error occurred (404) when calling the GetDurableExecution operation:
# Execution <uuid>%2F<invocation-id> not foundThe %2F literal in the error message is the giveaway — the WebServer is looking up the still-encoded form rather than the canonical ARN.
Why nothing caught it
- All routing tests use slash-free literals like
"test-arn"or"my-function", soRoute.from_stringnever produced a segment containing%XX. - All
boto3.clientusages in the test suite are mocked (patch("boto3.client")), so the real botocore serializer that does the encoding is never exercised. - The integration test in
tests/web/e2e/server_int_test.pyconstructsHTTPRequestobjects in-process and bypasses the HTTP/socket layer. - The headline e2e test (
tests/e2e/basic_success_path_test.py) usesDurableFunctionTestRunner, which doesn't go through the affected route layer. - CI runs only
hatch test/hatch fmt --check/hatch run types:check; no test exercised the boto→WebServer seam.
#117 fixed the same bug shape for callback IDs but the systemic fix (decode at the parser layer) was not applied at the time.
Expected fix
URL-decode captured path segments at the parser layer (Route.from_string) so every existing and future captured field inherits the behavior, and add a real-boto-through-WebServer regression test so the seam stays covered. PR forthcoming.
Metadata
Metadata
Assignees
Labels
Type
Projects
- StatusShow more project fieldsDone
Activity
[-]local-runner encoding / to %2F[/-][+][Bug]: WebServer route layer doesn't URL-decode path segments[/+]